Skip to content

Final Audit Opt#297

Merged
arul28 merged 24 commits into
mainfrom
ade/final-audit-opt-3575a904
May 14, 2026
Merged

Final Audit Opt#297
arul28 merged 24 commits into
mainfrom
ade/final-audit-opt-3575a904

Conversation

@arul28
Copy link
Copy Markdown
Owner

@arul28 arul28 commented May 14, 2026

Summary

Describe the change.

What Changed

Key files and behaviors.

Validation

How you tested.

Risks

Anything to watch.

Summary by CodeRabbit

  • New Features

    • Added support for discovering and using agent skills across projects
    • Enabled targeted refresh for individual pull requests
    • Added read-only mode for chat sessions accessed from different lanes
  • Improvements

    • Enhanced workspace selection to gracefully handle missing directories
    • Limited GitHub action runs fetching for better performance
    • Improved phone sync startup initialization and lifecycle
    • Refined CLI guidance documentation for ADE CLI onboarding
  • Bug Fixes

    • Fixed error handling when workspace roots become unavailable

Review Change Stack

Greptile Summary

This PR delivers a broad audit of ADE's agent infrastructure, touching skill discovery, chat session handling, file path security, CLI state management, and several UI panels.

  • Skill discovery: Expands to .agents/skills, .ade/skills, bundled app skills, and user skill roots; adds YAML frontmatter to exported SKILL.md files and moves the export target from .claude/skills to .ade/skills.
  • Security & stability: Fixes symlink traversal in transcript path validation (realpathSync), caps Claude daemon responses at 1 MB, adds an interrupt check to the background job poll loop, adds a file-lock (atomic rename) to the CLI state read-modify-write, and limits action-run fetching to 12 runs / 6 job detail fetches.
  • Performance & UX: Makes refreshState history hydration incremental (skips re-fetches when the session is unchanged), defers phone-sync subscription startup by 5 s, makes the Activity tab load lazily, adds targeted per-PR refresh, and gracefully falls back to the primary workspace when a worktree root goes missing.

Confidence Score: 4/5

Safe to merge; the changes are additive hardening and incremental optimisation with no breaking API changes.

All three findings are non-blocking quality notes. The phone-sync subscription teardown on drawer toggle is a minor UX regression (brief status flash, brief event gap). The bundledSkillRoots path-walk and the double-realpathSync in transcript reads are low-impact inefficiencies that do not affect correctness.

apps/desktop/src/renderer/components/app/TopBar.tsx (phoneSyncOpen dep), apps/desktop/src/main/services/chat/claudeSlashCommandDiscovery.ts (bundledSkillRoots depth), apps/desktop/src/main/services/chat/agentChatService.ts (resolveReadableChatPath double-realpathSync)

Important Files Changed

Filename Overview
apps/desktop/src/main/services/chat/agentChatService.ts Multiple security hardening patches: symlink traversal fix, 1 MB daemon response cap, new resolveReadableChatPath/resolveContainedChatPath helpers, interrupt checks in background job polling, and 20-item cap on injected project commands/skills. The double-realpathSync in resolveReadableChatPath is a minor inefficiency.
apps/ade-cli/src/tuiClient/state.ts Adds per-project state scoping and replaces the TOCTOU-prone read-modify-write with a file-lock mechanism using atomic rename writes. sleepSync uses Atomics.wait (blocks event loop up to 25ms per retry) but is contained in a debounced write path.
apps/desktop/src/main/services/chat/claudeSlashCommandDiscovery.ts Expands skill discovery to .agents/skills, .ade/skills, user skill roots, and bundled skills. bundledSkillRoots generates up to 18 speculative directory candidates via an 8-level walk. Logic is correct with proper deduplication.
apps/desktop/src/renderer/components/app/TopBar.tsx Adds 5s startup delay for phone sync and always-visible sync button. phoneSyncOpen in deps causes subscription teardown/recreate on drawer toggle, introducing a brief event gap and status flash.
apps/ade-cli/src/tuiClient/app.tsx Adds refreshGenerationRef to prevent stale async refreshState results from applying and optimises history hydration to skip re-fetches when hydrateHistory:false and session is unchanged.
apps/desktop/src/renderer/components/prs/state/PrsContext.tsx Adds targeted per-PR refresh support. On rate limit, cached state is now preserved instead of cleared, and liveDetailApplied is only set after all results succeed.
apps/desktop/src/renderer/components/prs/detail/PrDetailPane.tsx Activity tab loading is now lazy with derived activity from loaded checks/reviews/comments as immediate fallback. Promise.allSettled destructuring order correctly updated.
apps/desktop/src/main/services/prs/prService.ts Limits action run fetching to 12 runs and job detail fetching to 6 runs via new constants.
apps/desktop/src/renderer/components/files/FilesPage.tsx Adds unavailableWorkspaceIds to gracefully handle missing worktree roots with auto-fallback to primary workspace, and enables liveWatchEnabled whenever a workspace is selected.
apps/desktop/src/main/services/memory/skillRegistryService.ts Adds .agents/skills crawling, AGENTS.md as root doc, YAML frontmatter to generated SKILL.md, and moves skill export destination from .claude/skills to .ade/skills.
apps/ade-cli/src/tuiClient/connection.ts Multi-project mode now receives chat events via the runtime/event notification channel with a pending queue. replay:false suppresses historical event replay on reconnect.
apps/desktop/src/renderer/components/chat/ChatIosSimulatorPanel.tsx Adds controlDisabledReason prop for read-only mode when accessed from a different lane; all interaction paths check controlsOwnedElsewhere before executing.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[refreshState called] --> B{hydrateHistory option}
    B -- "false + same session" --> C[Skip getChatHistory]
    B -- "true OR session changed" --> D[getChatHistory]
    D --> E{isCurrentRefresh?}
    E -- No --> F[Abandon stale result]
    E -- Yes --> G[setEvents + loadedSessionIdRef update]
    C --> H[Update lanes/sessions/models]
    G --> H
Loading

Comments Outside Diff (1)

  1. apps/desktop/src/main/services/chat/agentChatService.ts, line 10383-10510 (link)

    P1 runClaudeBackgroundTurn has no call site

    runClaudeBackgroundTurn (and its entire dependency chain — dispatchClaudeBackgroundPrompt, monitorClaudeBackgroundJob, buildClaudeBackgroundPromptText, buildClaudeBackgroundArgs, buildClaudeBackgroundSystemPrompt) is defined but never invoked anywhere in the codebase. The turn-dispatch router at line ~10525 still routes all Claude sessions to runClaudeTurn. This adds ~600 lines of complex logic that is currently unreachable code. Is this intentional prep work missing its dispatch hookup, or should runClaudeBackgroundTurn be wired into the provider selection block?

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/desktop/src/main/services/chat/agentChatService.ts
    Line: 10383-10510
    
    Comment:
    **`runClaudeBackgroundTurn` has no call site**
    
    `runClaudeBackgroundTurn` (and its entire dependency chain — `dispatchClaudeBackgroundPrompt`, `monitorClaudeBackgroundJob`, `buildClaudeBackgroundPromptText`, `buildClaudeBackgroundArgs`, `buildClaudeBackgroundSystemPrompt`) is defined but never invoked anywhere in the codebase. The turn-dispatch router at line ~10525 still routes all Claude sessions to `runClaudeTurn`. This adds ~600 lines of complex logic that is currently unreachable code. Is this intentional prep work missing its dispatch hookup, or should `runClaudeBackgroundTurn` be wired into the provider selection block?
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code

Fix All in Claude Code

Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
apps/desktop/src/renderer/components/app/TopBar.tsx:854-914
**`phoneSyncOpen` in deps tears down the sync subscription on every drawer toggle**

Adding `phoneSyncOpen` to the `useEffect` dependency array means the entire effect — including `setSyncSnapshot(null)`, subscription teardown, and re-creation — runs whenever the user opens or closes the phone sync drawer. On close, the startup timer starts with the full `PHONE_SYNC_STARTUP_DELAY_MS` (5 seconds), leaving a 5-second window where the subscription is gone and any incoming `sync-status` events are silently dropped. On open, `setSyncSnapshot(null)` fires first, briefly clearing the displayed status label before the immediate `startSyncStatus()` repopulates it.

### Issue 2 of 3
apps/desktop/src/main/services/chat/claudeSlashCommandDiscovery.ts:271-290
**`bundledSkillRoots` walks up 8 directory levels with two path variants per level**

The function generates up to `2 + (8 × 2) = 18` candidate paths by resolving `resources/agent-skills` and `apps/desktop/resources/agent-skills` at every ancestor of `__dirname`. Most of these paths won't exist, but each is probed by `discoverSkills` at startup. Capping this to 3–4 levels would reduce filesystem probing without affecting correctness.

### Issue 3 of 3
apps/desktop/src/main/services/chat/agentChatService.ts:6800-6830
**`resolveReadableChatPath` calls `resolveContainedChatPath` twice, running `realpathSync` twice per transcript read**

The helper calls `resolveContainedChatPath` with `allowMissing=true` to check validity, flushes pending writes, then calls it again with `allowMissing=false` for the real path. Each call independently invokes `safeRealpath` on the target, `layout.adeDir`, and `transcriptsDir`. With high-frequency callers like `readTranscriptConversationEntries` and `readTranscriptEntries`, these redundant syscalls accumulate. Caching the resolved path from the first call and threading it into the second would eliminate the redundancy.

Reviews (3): Last reviewed commit: "fix(audit): address release gate review ..." | Re-trigger Greptile

@vercel
Copy link
Copy Markdown

vercel Bot commented May 14, 2026

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
ade Ignored Ignored Preview May 14, 2026 9:40am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 14, 2026

📝 Walkthrough

Walkthrough

This PR extends Claude slash-command and skill discovery with skill-root search logic, implements read-only control gating for cross-lane chat panels via controlDisabledReason, adds targeted PR refresh support, filters unavailable workspace roots, introduces startup delays for deferred initialization, limits GitHub action run/job fetching, refactors run-page multi-target launch tracking, updates skill registry paths and AGENTS.md detection, and expands system-prompt assertions.

Changes

Claude Slash Commands and Skills Discovery

Layer / File(s) Summary
Skill root discovery helpers and precedence
apps/desktop/src/main/services/chat/claudeSlashCommandDiscovery.ts
Adds ancestorSkillRoots() and skillRootsByPrecedence() helpers to compute ordered directories containing skills from ancestors, .agents/.ade, and bundled agent-skills resource locations.
Slash command and skill discovery
apps/desktop/src/main/services/chat/claudeSlashCommandDiscovery.ts
Updates command discovery to search legacy command files first, then user-invocable skills from computed skill roots; deduplicates by normalized name and sorts by source priority (command before skill) then name.
Command and skill discovery test coverage
apps/desktop/src/main/services/chat/claudeSlashCommandDiscovery.test.ts
Expands tests to assert argumentHint, colon-delimited nested paths, skill source metadata, bundled resource discovery, and frontier-cap command inclusion.

Chat Service Claude Session and Skill Integration

Layer / File(s) Summary
Claude session ID capture and synchronization
apps/desktop/src/main/services/chat/agentChatService.ts
Captures trimmed session_id from general provider messages and system:init events; mirrors to session pointer and persists chat state on change.
Project command and skill injection limits
apps/desktop/src/main/services/chat/agentChatService.ts, apps/desktop/src/main/services/chat/agentChatService.test.ts
Adds MAX_INJECTED_PROJECT_COMMANDS/SKILLS, slices visible sets, emits "hidden" counts in injected prompt; updates assertions for bundled ADE skills and command capping.
Claude provider-based session routing
apps/desktop/src/main/services/chat/agentChatService.ts
Broadens Claude-specific runtime entry points to key off managed.session.provider === "claude" for steer queuing, interruption handling, and prewarming logic.
Claude prewarming and fork/handoff flow
apps/desktop/src/main/services/chat/agentChatService.ts
Tightens prewarming guards on provider/model match, session status, and runtime state; updates fork path to preserve SDK session ID, mirror pointer, and trigger prewarming.
Agent chat service test updates
apps/desktop/src/main/services/chat/agentChatService.test.ts
Refactors SDK session mocking, adds identity-continuity and bypass-permissions scenarios, updates assertions for bundled skills and command limits.

Read-Only Control Gating for Cross-Lane Sessions

Layer / File(s) Summary
ChatAppControlPanel read-only state
apps/desktop/src/renderer/components/chat/ChatAppControlPanel.tsx, apps/desktop/src/renderer/components/chat/ChatAppControlPanel.test.tsx
Adds controlDisabledReason prop, derives controlsDisabled flag, blocks user actions (launch, attach, connect, stop, click, re-attach, snapshot, mode toggle), disables UI controls, and includes test coverage.
ChatIosSimulatorPanel read-only state
apps/desktop/src/renderer/components/chat/ChatIosSimulatorPanel.tsx, apps/desktop/src/renderer/components/chat/ChatIosSimulatorPanel.test.tsx
Adds controlDisabledReason prop, derives controlsOwnedElsewhere state, blocks simulator control paths (launch, shutdown, typing, interactions), disables/hides UI controls, and includes test coverage.
WorkSidebar lane-mismatch gating propagation
apps/desktop/src/renderer/components/terminals/WorkSidebar.tsx, apps/desktop/src/renderer/components/terminals/WorkSidebar.test.tsx
Computes laneMismatchReason once and passes as controlDisabledReason to both iOS Simulator and App Control panels; updates test mocks and assertions.

Workspace Availability Filtering and File Watching

Layer / File(s) Summary
File service workspace root filtering
apps/desktop/src/main/services/files/fileService.ts, apps/desktop/src/main/services/files/fileService.test.ts
Adds workspaceRootExists helper; filters non-primary scopes whose filesystem roots are missing while keeping primary workspace first.
Files page workspace availability handling
apps/desktop/src/renderer/components/files/FilesPage.tsx, apps/desktop/src/renderer/components/files/FilesPage.test.tsx
Tracks unavailableWorkspaceIds, falls back to primary on missing-root errors, excludes unavailable workspaces from lane/suggestion lists, enables watching based on workspace ID, and includes test coverage.
Conflict pane layout for embedded rendering
apps/desktop/src/renderer/components/files/FilesPage.tsx
Refactors conflict layout markup and sizing to distinguish embedded vs split rendering with wrapper/scroll regions and minWidth constraints.

Targeted PR Refresh Infrastructure

Layer / File(s) Summary
PrsContext targeted refresh support
apps/desktop/src/renderer/components/prs/state/PrsContext.tsx
Updates refresh method to accept optional prId/prIds args, normalizes into githubRefreshArgs, and passes through to window.ade.prs.refresh().
GitHub rate-limit graceful degradation
apps/desktop/src/renderer/components/prs/state/PrsContext.tsx, apps/desktop/src/renderer/components/prs/state/PrsContext.test.tsx
Changes rate-limit handling to clear only detailLiveDataPrId while preserving cached snapshot/detail state; adds test for stale-data visibility during backoff.
PRsPage and PR detail pane refresh targeting
apps/desktop/src/renderer/components/prs/PRsPage.tsx, apps/desktop/src/renderer/components/prs/detail/PrDetailPane.tsx
Adds PrRefreshArgs type, updates handleRefresh to support targeted PR refresh with conditional lane refresh, and updates onRefresh prop contract.
PR detail activity construction and polling
apps/desktop/src/renderer/components/prs/detail/PrDetailPane.tsx, apps/desktop/src/renderer/components/prs/detail/PrDetailPane.issueResolver.test.tsx
Introduces buildActivityFromLoadedDetail helper, loadedDetailActivityRef for memoized snapshot, refactors activity polling to fetch full activity only when Activity tab is visible, and adds detail refresh test.
GitHubTab targeted refresh and error handling
apps/desktop/src/renderer/components/prs/tabs/GitHubTab.tsx, apps/desktop/src/renderer/components/prs/tabs/GitHubTab.test.tsx
Extends onRefreshAll prop to accept optional prId/prIds, adds formatGitHubSnapshotError helper for token-missing detection, conditionally performs targeted vs full refresh, and includes token-missing error test.

Run Page Multi-Target Pending Launch Tracking

Layer / File(s) Summary
PendingRunLaunch multi-target type and event handler
apps/desktop/src/renderer/components/run/RunPage.tsx
Adds PendingRunLaunch type with targets array, refactors runtime event handler to match lane+process and clear individual targets, introduces target-clearing helpers.
Run launch handlers for single and group targets
apps/desktop/src/renderer/components/run/RunPage.tsx, apps/desktop/src/renderer/components/run/RunPage.test.tsx
Updates handleRun to set pending targets array and clear matched target on failure; updates handleRunGroupAll to compute launchTargets for group and clear all on failure; adds group run test.

Deferred Startup Initialization for Background Operations

Layer / File(s) Summary
TopBar phone-sync delayed startup
apps/desktop/src/renderer/components/app/TopBar.tsx, apps/desktop/src/renderer/components/app/TopBar.test.tsx
Adds PHONE_SYNC_STARTUP_DELAY_MS, refactors sync-status effect with delayed timer and started flag, shows control based on workspace project state not snapshot presence, includes fake-timer test coverage.
LinearQuickViewButton initial visibility check delay
apps/desktop/src/renderer/components/app/LinearQuickViewButton.tsx
Adds INITIAL_VISIBILITY_CHECK_DELAY_MS, schedules loadVisibility via setTimeout, updates cleanup to clear timer.
HeaderUsageControl deferred snapshot and provider status reads
apps/desktop/src/renderer/components/usage/HeaderUsageControl.tsx
Adds HEADER_USAGE_STARTUP_DELAY_MS, refactors both snapshot/subscription and provider-status effects to defer with open-dependent setTimeout.

GitHub Action Run and Job Fetch Limiting

Layer / File(s) Summary
PR action run and job fetch limits
apps/desktop/src/main/services/prs/prService.ts, apps/desktop/src/main/services/prs/prService.test.ts
Adds PR_ACTION_RUNS_LIMIT and PR_ACTION_RUN_JOBS_LIMIT, updates getActionRuns to fetch runs with per_page limit, selectively fetch jobs for first N runs, and degrade gracefully on job-fetch failures.

Remote Runtime Connection Pool Retry Improvements

Layer / File(s) Summary
Remote sync request retry with connection handling
apps/desktop/src/main/services/remoteRuntime/remoteConnectionPool.ts, apps/desktop/src/main/services/remoteRuntime/remoteConnectionPool.test.ts
Changes callSyncForTarget to execute via withEntryForTarget with projectId injection and retryOnConnectionError flag; includes retry test.

Skill Registry Indexing and Export Path Updates

Layer / File(s) Summary
Expanded skill discovery and watching paths
apps/desktop/src/main/services/memory/skillRegistryService.ts
Adds .agents/skills watching, updates inferKind to use basename matching for AGENTS.md/CLAUDE.md case-insensitive detection.
Skill markdown generation and export paths
apps/desktop/src/main/services/memory/skillRegistryService.ts
Adds YAML frontmatter block with name/description to generated skills; changes exported skill destination from .claude/skills/ to .ade/skills/.
Skill indexing and procedure import/linking
apps/desktop/src/main/services/memory/skillRegistryService.ts, apps/desktop/src/main/services/memory/skillRegistryService.test.ts
Refines import/linking so non-root skills are only imported when source is not "exported"; adds path crawling for .agents/skills, .claude/skills, .claude/commands; includes AGENTS.md root doc test.

System Prompt Assertions and CLI Guidance Updates

Layer / File(s) Summary
System prompt operating loop and skills assertions
apps/desktop/src/main/services/ai/tools/systemPrompt.test.ts
Replaces CLI command assertions with ADE Skills references and control-plane identifiers.
ADE CLI guidance content condensing
apps/desktop/src/shared/adeCliGuidance.ts
Rewrites ADE_CLI_AGENT_GUIDANCE with shorter Discovery/Skills/Socket sections; condenses ADE_CLI_INLINE_GUIDANCE examples and skills list.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • arul28/ADE#221: Both PRs modify iOS Simulator UI control gating and ADE CLI guidance in shared files.
  • arul28/ADE#259: Both PRs touch TopBar phone-sync lifecycle including startup delay, polling, and focus-based refresh behavior.
  • arul28/ADE#222: Both PRs refactor RunPage pending-target handling and runtime event cleanup for launch tracking.

Poem

🐰 Whiskers twitch with joy at this refactor spree,
Skills now bloom from roots in .ade/.agents trees,
Panels read-only when lanes don't agree,
Workspaces filter missing, PR refreshes run free!
Startup delays defer, runtime retries flow,
What a delightful expansion—the codebase doth grow!

✨ 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 ade/final-audit-opt-3575a904

@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented May 14, 2026

@copilot review but do not make fixes

arul28 added 21 commits May 14, 2026 05:04
boot open-project score 694.26 -> 204.38; removed ade.cto.getLinearConnectionStatus 2039ms from startup summary
Adapt multi-project runtime chat envelopes from runtime/event into the TUI onChatEvent stream; verified attached print-state and focused connection tests.
boot.open-project fitness 902.72 -> 53.71; localRuntime.callAction p95 2025ms -> 97ms; slow IPC channels 2 -> 0.
Prevents last lane/chat choices from one project influencing another when lane ids overlap. Keeps legacy global keys as migration fallback.
Copy link
Copy Markdown
Contributor

Copilot AI commented May 14, 2026

@copilot review but do not make fixes

Reviewed at c2c9cb6 with no code changes made, per request. I don’t see an immediate correctness blocker from a spot audit of the recent commits, but this PR is very broad (235 files), so I recommend narrowing follow-up audits to specific areas (Work, PRs, Files, or release workflow) for deeper confidence.

Copilot finished work on behalf of arul28 May 14, 2026 09:05
Comment thread apps/desktop/src/main/services/chat/agentChatService.ts
Comment thread apps/desktop/src/main/services/chat/agentChatService.ts
Comment thread apps/desktop/src/main/services/chat/agentChatService.ts
@arul28 arul28 force-pushed the ade/final-audit-opt-3575a904 branch from c2c9cb6 to 7bbba1d Compare May 14, 2026 09:18
@capy-ai
Copy link
Copy Markdown

capy-ai Bot commented May 14, 2026

Capy auto-review is paused for this organization because the monthly auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews.

@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented May 14, 2026

@copilot review but do not make fixes

Comment thread apps/ade-cli/src/tuiClient/state.ts
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: 7

Caution

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

⚠️ Outside diff range comments (3)
apps/desktop/src/renderer/components/prs/state/PrsContext.tsx (1)

777-780: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Queued targeted refreshes lose their PR scope while a refresh is in flight.

When refreshInFlight.current is true, only refreshPending is recorded. The follow-up run at Line 826 always drops githubRefreshArgs, so a queued targeted refresh becomes an unscoped refresh.

💡 Suggested fix
+const pendingGithubRefreshArgsRef = React.useRef<{ prId?: string; prIds?: string[] } | undefined>(undefined);
+
+function mergeGithubRefreshArgs(
+  a?: { prId?: string; prIds?: string[] },
+  b?: { prId?: string; prIds?: string[] },
+): { prId?: string; prIds?: string[] } | undefined {
+  const ids = [
+    ...(a?.prId ? [a.prId] : []),
+    ...(a?.prIds ?? []),
+    ...(b?.prId ? [b.prId] : []),
+    ...(b?.prIds ?? []),
+  ].map((id) => String(id ?? "").trim()).filter(Boolean);
+  const unique = [...new Set(ids)];
+  if (unique.length === 0) return undefined;
+  return unique.length === 1 ? { prId: unique[0] } : { prIds: unique };
+}
+
 if (refreshInFlight.current) {
   refreshPending.current = true;
+  pendingGithubRefreshArgsRef.current = mergeGithubRefreshArgs(
+    pendingGithubRefreshArgsRef.current,
+    options.githubRefreshArgs,
+  );
   return;
 }
 ...
 if (refreshPending.current) {
   refreshPending.current = false;
-  void refreshCore({ githubRefreshMode: "await" });
+  const queuedArgs = pendingGithubRefreshArgsRef.current;
+  pendingGithubRefreshArgsRef.current = undefined;
+  void refreshCore({ githubRefreshMode: "await", githubRefreshArgs: queuedArgs });
 }

Also applies to: 824-827, 831-842

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/renderer/components/prs/state/PrsContext.tsx` around lines
777 - 780, The queued targeted refresh loses its PR scope because when
refreshInFlight.current is true the code only sets refreshPending.current = true
and discards githubRefreshArgs; change this to also save the pending refresh
arguments (e.g., set a new or existing variable like pendingGithubRefreshArgs =
githubRefreshArgs or assign refreshPendingArgs.current = githubRefreshArgs)
whenever refreshPending.current is set, and update the follow-up refresh runner
(the logic that checks refreshPending.current and clears githubRefreshArgs) to
prefer and consume pendingGithubRefreshArgs/current instead of always resetting
to an unscoped refresh; apply the same pattern where similar early-return logic
appears (the blocks around the refreshInFlight checks at lines referenced:
824-827, 831-842) so queued targeted refreshes retain their scope.
apps/desktop/src/renderer/components/app/TopBar.tsx (1)

854-914: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Closing the phone-sync drawer wipes the snapshot and drops the subscription for 5 s.

Because phoneSyncOpen is in this effect's dep array, every drawer toggle (including the close path) tears down the disposeSyncEvents subscription and re-runs the body, which calls setSyncSnapshot(null) and schedules a fresh 5 s startup timer. The result:

  • After a user closes the drawer, the header label flickers from e.g. "1 phone connected to ADE Desktop" back to the "Phone sync" fallback and the dot reverts to bg-white/30 for ~5 seconds before the new refreshSyncStatus repopulates it.
  • During that 5 s window there is no sync.onEvent subscription, so any sync-status event broadcast by the main process is lost.
  • Even the open path tears down a perfectly fresh subscription only to immediately rebuild it.

The in-code comment ("Focus and explicit drawer opens still refresh immediately") suggests you only want an immediate refresh when the drawer opens, not a full subscription rebuild on every toggle. Suggest moving the "expedite startup on open" trigger into a separate effect (or a startedRef) and keeping the subscription effect keyed only on project?.rootPath / remoteBinding:

♻️ Sketch of one possible split
   useEffect(() => {
     let cancelled = false;
     let statusRequestVersion = 0;
     let started = false;
     let startupTimer: number | null = null;
     let disposeSyncEvents: (() => void) | null = null;
     if (!project?.rootPath || remoteBinding) {
       setSyncSnapshot(null);
       setPhoneSyncOpen(false);
       return () => {
         cancelled = true;
       };
     }
     const refreshSyncStatus = () => { /* unchanged */ };
-    setSyncSnapshot(null);
+    // Don't wipe an existing snapshot on re-run; refreshSyncStatus will
+    // replace it once the new request lands.
     const startSyncStatus = () => { /* unchanged */ };
     const onFocus = () => {
       if (started) refreshSyncStatus();
       else startSyncStatus();
     };
-    startupTimer = window.setTimeout(
-      startSyncStatus,
-      phoneSyncOpen ? 0 : PHONE_SYNC_STARTUP_DELAY_MS,
-    );
+    startupTimer = window.setTimeout(startSyncStatus, PHONE_SYNC_STARTUP_DELAY_MS);
     window.addEventListener("focus", onFocus);
+    startSyncStatusRef.current = startSyncStatus;
     return () => {
       cancelled = true;
       if (startupTimer != null) window.clearTimeout(startupTimer);
       window.removeEventListener("focus", onFocus);
       disposeSyncEvents?.();
+      startSyncStatusRef.current = null;
     };
-  }, [phoneSyncOpen, project?.rootPath, remoteBinding]);
+  }, [project?.rootPath, remoteBinding]);
+
+  // Expedite the first fetch when the drawer is explicitly opened.
+  useEffect(() => {
+    if (phoneSyncOpen) startSyncStatusRef.current?.();
+  }, [phoneSyncOpen]);

(startSyncStatusRef would be a useRef<(() => void) | null>(null) defined alongside the existing refs.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/renderer/components/app/TopBar.tsx` around lines 854 - 914,
The effect currently depends on phoneSyncOpen which causes teardown of the
subscription and setSyncSnapshot(null) on every drawer toggle; instead split the
behavior: keep the subscription effect (the useEffect body using
startSyncStatus, refreshSyncStatus, disposeSyncEvents, statusRequestVersion,
cancelled, startupTimer) keyed only on project?.rootPath and remoteBinding so
toggling the drawer does not recreate the subscription, and create a small
separate effect that watches phoneSyncOpen and calls startSyncStatus (or
refreshSyncStatus via a startSyncStatusRef) immediately when the drawer opens
without calling setSyncSnapshot(null) or disposing the existing subscription;
use a ref (e.g., startedRef or startSyncStatusRef) to expose startSyncStatus to
that second effect so you can trigger an immediate refresh on open while
preserving the long-lived subscription and avoiding the 5s teardown gap.
apps/desktop/src/renderer/components/run/RunPage.tsx (1)

884-909: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Confirm multi-target group launch behavior and add test coverage for it.

In a group "Run all" launch, when any process emits a runtime event with sessionId/ptyId populated, revealRuntimeTerminal returns true and line 897 sets pendingRunLaunchRef.current = null, immediately clearing all remaining targets. Subsequent runtime events for group siblings then short-circuit at line 889 (if (!pending) return;) and never reach the !isActiveProcessStatus cleanup branch.

The current test only emits a single runtime event, so it doesn't exercise the multi-target case. This behavior may be intentional (prevent foreground-stealing after the first terminal reveals), but it's worth confirming. If the intent is to keep tracking remaining targets for cleanup or to allow multiple terminals to surface, consider filtering only the matched target on success instead of nulling the entire ref:

♻️ Optional: filter only the matched target on reveal
       if (revealRuntimeTerminal(event.runtime)) {
-        pendingRunLaunchRef.current = null;
-        return;
+        const remaining = pending.targets.filter((_, index) => index !== targetIndex);
+        pendingRunLaunchRef.current = remaining.length > 0 ? { targets: remaining } : null;
+        return;
       }

Add a test that emits runtime events for both processes and assert whether the second one is/isn't revealed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/renderer/components/run/RunPage.tsx` around lines 884 - 909,
The current onEvent handler nulls the entire pendingRunLaunchRef when
revealRuntimeTerminal(event.runtime) returns true, which cancels remaining group
targets; update the logic to only remove the matched target from
pendingRunLaunchRef.current (using the found targetIndex) instead of setting it
to null so sibling runtime events can still be processed and cleaned up, and add
a unit/integration test that emits runtime events for both targets to assert
whether the second process is revealed or only cleaned up; focus changes around
the pendingRunLaunchRef usage, the revealRuntimeTerminal call, the targetIndex
calculation, and the cleanup branch that uses
isActiveProcessStatus(event.runtime.status).
🧹 Nitpick comments (5)
apps/desktop/src/main/services/chat/claudeSlashCommandDiscovery.ts (1)

275-287: 💤 Low value

Add guard for __dirname to match codebase patterns.

bundledSkillRoots() calls path.resolve(__dirname, ...) at lines 283–284 without guarding. While the main process compiles to CommonJS (.cjs), other files in the same codebase already protect __dirname with typeof __dirname !== "string" checks (e.g., openCodeBinaryManager.ts, adeCliService.ts). For consistency and defensive programming, add the same guard:

if (typeof __dirname !== "string") {
  // fallback for ESM context
  return [];  // or use fileURLToPath(import.meta.url) alternative
}

Additionally, consider memoizing the resolved roots—the function generates ~18 candidate paths on every call, though fs.existsSync filters them downstream.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/main/services/chat/claudeSlashCommandDiscovery.ts` around
lines 275 - 287, bundledSkillRoots currently uses __dirname unguarded which can
break in ESM contexts; add a guard at the top of bundledSkillRoots (check typeof
__dirname !== "string") and return a safe fallback (e.g., [] or derive a path
via fileURLToPath(import.meta.url)) to mirror the pattern used in
openCodeBinaryManager.ts / adeCliService.ts, and memoize the computed candidate
array (cache in a module-level variable) so repeated calls reuse the ~18
resolved paths instead of rebuilding them each time; reference bundledSkillRoots
and __dirname when making these changes.
apps/desktop/src/main/services/chat/agentChatService.ts (3)

14829-14837: ⚡ Quick win

Prewarm gated on runtime?.kind === "claude" may skip Claude-provider sessions whose runtime hasn't been created yet.

Hunks 4 and 9 broadened other Claude dispatch points to key off managed.session.provider === "claude", but the fork branch here still gates the reset/prewarm on createdManaged.runtime?.kind === "claude". For a freshly ensureManagedSession'd session the runtime may not yet exist, in which case claudeBackgroundResumeSessionId and the pointer are mirrored but no prewarm fires for what is effectively a Claude session. Consider switching the inner gate to a provider check (and using ensureClaudeSessionRuntime) to match the rest of the file.

♻️ Suggested adjustment
     if (handoffMode === "fork") {
       createdManaged.claudeBackgroundResumeSessionId = sourceSdkSessionId;
       mirrorClaudeSessionPointer(createdManaged, sourceSdkSessionId);
-      if (createdManaged.runtime?.kind === "claude") {
-        resetClaudeQuerySession(createdManaged, createdManaged.runtime, "session_reset", { clearSdkSessionId: true });
-        createdManaged.runtime.forkFromSdkSessionId = sourceSdkSessionId;
+      if (createdManaged.session.provider === "claude") {
+        const createdRuntime = ensureClaudeSessionRuntime(createdManaged);
+        resetClaudeQuerySession(createdManaged, createdRuntime, "session_reset", { clearSdkSessionId: true });
+        createdRuntime.forkFromSdkSessionId = sourceSdkSessionId;
         prewarmClaudeQuery(createdManaged);
       }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/main/services/chat/agentChatService.ts` around lines 14829 -
14837, The fork branch currently checks createdManaged.runtime?.kind ===
"claude" which can miss freshly-created Claude sessions without a runtime;
change the inner condition to check createdManaged.session.provider === "claude"
and call ensureClaudeSessionRuntime(createdManaged) to create/return the runtime
before invoking resetClaudeQuerySession, setting
createdManaged.runtime.forkFromSdkSessionId and calling prewarmClaudeQuery, so
that mirrorClaudeSessionPointer and claudeBackgroundResumeSessionId are mirrored
but the reset/prewarm always run for Claude-provider sessions even if the
runtime did not yet exist.

18883-18898: 💤 Low value

Redundant null check on short.

managed.claudeBackgroundJobShort was already proven truthy in the outer condition, so the inner if (short) can never be false. Drop the redundant check (and either inline the variable or keep it for readability).

♻️ Suggested simplification
     if (managed.session.provider === "claude" && managed.claudeBackgroundJobShort && managed.runtime?.kind !== "claude") {
       const short = managed.claudeBackgroundJobShort;
-      if (short) {
-        await runClaudeBackgroundCliCommand(managed, ["stop", short], 15_000).catch((error) => {
-          logger.warn("agent_chat.claude_background_stop_failed", {
-            sessionId,
-            short,
-            error: error instanceof Error ? error.message : String(error),
-          });
-        });
-      }
+      await runClaudeBackgroundCliCommand(managed, ["stop", short], 15_000).catch((error) => {
+        logger.warn("agent_chat.claude_background_stop_failed", {
+          sessionId,
+          short,
+          error: error instanceof Error ? error.message : String(error),
+        });
+      });
       markSessionIdleWithFreshCache(managed);
       persistChatState(managed);
       return;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/main/services/chat/agentChatService.ts` around lines 18883 -
18898, The inner null-check on short is redundant because
managed.claudeBackgroundJobShort is already tested in the outer if; remove the
inner if (short) and directly call await runClaudeBackgroundCliCommand(managed,
["stop", short], 15_000).catch(...). Keep the short local variable if you prefer
readability (or inline managed.claudeBackgroundJobShort into
runClaudeBackgroundCliCommand), and ensure the logger.warn,
markSessionIdleWithFreshCache(managed), and persistChatState(managed) behavior
remains unchanged.

20600-20607: 💤 Low value

ensureClaudeSessionRuntime mutates state on first invocation even when subsequent guards bail.

ensureClaudeSessionRuntime(managed) materializes a new Claude runtime regardless of whether prewarmClaudeQuery will actually execute. The function:

  1. Calls evictLeastRecentRuntime() if at capacity (line 14160)—a side effect modifying global managedSessions state
  2. Creates and assigns a new runtime object to managed.runtime (line 14181)—mutating the managed session

While the function has an early return for existing runtimes (line 14157), on first invocation these side effects occur unconditionally. Since the subsequent checks (runtime.busy, runtime.query, runtime.warmQuery, runtime.warmupDone) at lines 20601-20605 can cause early returns before prewarmClaudeQuery is called, consider materializing the runtime only when actually proceeding to prewarming to avoid unnecessary state allocation and eviction cycles.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/main/services/chat/agentChatService.ts` around lines 20600 -
20607, The current code calls ensureClaudeSessionRuntime(managed) which may
evict or allocate a new runtime even when later guards bail; change the logic to
avoid creating/mutating runtime unless you will actually prewarm: first check
for an existing runtime via managed.runtime (or add a non-mutating accessor like
getClaudeSessionRuntimeIfExists/peekClaudeSessionRuntime) and only call
ensureClaudeSessionRuntime(managed) when those guards pass and you are about to
call prewarmClaudeQuery(managed); this prevents evictLeastRecentRuntime() and
assignment to managed.runtime happening unnecessarily and keeps
persistChatState(managed) paired only with true prewarm actions.
apps/desktop/src/main/services/prs/prService.ts (1)

6462-6464: ⚡ Quick win

Add lightweight logging for skipped job details on fetch failure.

At Line 6462, the catch block silently drops job data. When jobs disappear in UI, this becomes hard to diagnose. Log a warning with PR/run context before returning empty jobs.

Proposed patch
-            } catch {
-              // Jobs fetch failed; return empty jobs array
+            } catch (error) {
+              logger.warn("prs.action_run_jobs_fetch_failed", {
+                prId,
+                runId,
+                error: getErrorMessage(error),
+              });
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/main/services/prs/prService.ts` around lines 6462 - 6464,
The empty catch at the jobs fetch silently swallows failures; update the catch
in prs/prService.ts (the block that currently returns an empty jobs array) to
log a lightweight warning before returning, using the repository/PR/run context
available in scope (e.g., PR id, run id, commit SHA) and include the caught
error message/stack; use the existing logger instance in this module (e.g.,
logger or processLogger) so the warning reads like "Failed to fetch jobs for PR
<prId>/run <runId> (sha=<sha>): <error>" and then return the empty jobs array as
before.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/desktop/src/main/services/chat/agentChatService.test.ts`:
- Around line 3304-3312: Tighten the assertions to check the deterministic SDK
IDs used in the test: replace the loose expect.any(String) on
claudeSdkResumeSessionCompat to assert the exact stale ID string ("sdk-stale")
as the first argument, and replace the loose expect.any(String) on the persisted
SDK session value from readPersistedChatState(session.id).sdkSessionId to assert
the exact fresh ID string ("sdk-fresh"); keep the existing checks for
claudeSdkCreateSessionCompat, staleSession.close, and freshSession.send
unchanged.

In `@apps/desktop/src/main/services/chat/agentChatService.ts`:
- Around line 9167-9186: The system:init branch assigns initMsg.session_id
without trimming which can overwrite the already-trimmed runtime.sdkSessionId;
change the logic in the system:init block to trim initMsg.session_id once into a
local (e.g., trimmedSessionId), use that for the length check and comparison
against runtime.sdkSessionId, and assign runtime.sdkSessionId = trimmedSessionId
before calling mirrorClaudeSessionPointer(managed, runtime.sdkSessionId) and
persistChatState(managed); keep reportedInitModel =
normalizeReportedModelName(initMsg.model) unchanged.

In `@apps/desktop/src/main/services/memory/skillRegistryService.ts`:
- Around line 145-148: The description frontmatter interpolates user data
directly (see slugify(input.title) and the `description: Use this skill when
${input.trigger.trim() || "the workflow applies"}.` line), which risks YAML
injection; fix by producing a YAML-safe value for the description instead of raw
interpolation — either escape/quote input.trigger (e.g., wrap the description in
a proper quoted string or escape YAML special chars) or, preferably, build the
entire frontmatter with a YAML serializer (yaml.safeDump / YAML.stringify) using
input.title and input.trigger so the library handles quoting/escaping for you.

In `@apps/desktop/src/main/services/prs/prService.test.ts`:
- Around line 1109-1172: The mock workflowRuns are generated oldest-first but
GitHub returns runs newest-first, so update the test to return runs in
descending created_at order: reverse the workflowRuns array (or generate it from
20 down to 1) before returning it from the githubService.apiRequest handler for
path "/repos/test-owner/test-repo/actions/runs"; this ensures
service.getActionRuns hydrates jobs for the newest 6 runs and the assertions
about calls to githubService.apiRequest and job-fetching (the
/actions/runs/:id/jobs requests) match real API behavior.

In `@apps/desktop/src/renderer/components/chat/ChatIosSimulatorPanel.tsx`:
- Around line 2224-2227: Handlers that currently only check controlsDisabled
(e.g., the branches that call setMessage(controlsDisabledMessage) and return)
must also prevent input when controls are owned remotely: extend the guard to
check ownedByOtherChat (and any lane-mismatch reason) before calling
typeText/tap/drag so remote ownership blocks input; update the message to
reflect ownership (use ownedByOtherChat-specific message or compose from
controlsDisabledMessage) and apply this change in the handlers referencing
controlsDisabled around the setMessage calls and the functions that call
typeText/tap/drag to ensure clicks/typing/dragging are blocked when
ownedByOtherChat is true.

In `@apps/desktop/src/renderer/components/prs/detail/PrDetailPane.tsx`:
- Around line 867-869: The code only hydrates activity when prev.length === 0
causing stale activity after PR switches; change the setActivity call to always
rebuild from the loaded details instead of conditionally using prev: replace
setActivity((prev) => prev.length > 0 ? prev :
buildActivityFromLoadedDetail(loaded.checks, loaded.reviews, loaded.comments))
with setActivity(buildActivityFromLoadedDetail(loaded.checks, loaded.reviews,
loaded.comments)) (apply same fix at the other occurrence around lines 956-958),
referencing loadedDetailActivityRef, setActivity, and
buildActivityFromLoadedDetail so the new PR's checks/reviews/comments are used
immediately.
- Around line 314-340: The synthesized event IDs in PrDetailPane.tsx (the loops
over reviews and checks that push into events) are not unique (using
`review-${review.reviewer}-${review.submittedAt}` and `ci-${check.name}`) and
can collide; change the ID generation in those push calls to include a unique
discriminator such as the loop index or an intrinsic unique field (e.g.,
`review.id` / `check.id` if available) or a deterministic hash of the object, so
each event entry in the `events` array has a stable unique `id`; update both the
review and check push sites (the for (const review of reviews) and for (const
check of checks) blocks) to produce unique IDs and keep the rest of the payload
unchanged.

---

Outside diff comments:
In `@apps/desktop/src/renderer/components/app/TopBar.tsx`:
- Around line 854-914: The effect currently depends on phoneSyncOpen which
causes teardown of the subscription and setSyncSnapshot(null) on every drawer
toggle; instead split the behavior: keep the subscription effect (the useEffect
body using startSyncStatus, refreshSyncStatus, disposeSyncEvents,
statusRequestVersion, cancelled, startupTimer) keyed only on project?.rootPath
and remoteBinding so toggling the drawer does not recreate the subscription, and
create a small separate effect that watches phoneSyncOpen and calls
startSyncStatus (or refreshSyncStatus via a startSyncStatusRef) immediately when
the drawer opens without calling setSyncSnapshot(null) or disposing the existing
subscription; use a ref (e.g., startedRef or startSyncStatusRef) to expose
startSyncStatus to that second effect so you can trigger an immediate refresh on
open while preserving the long-lived subscription and avoiding the 5s teardown
gap.

In `@apps/desktop/src/renderer/components/prs/state/PrsContext.tsx`:
- Around line 777-780: The queued targeted refresh loses its PR scope because
when refreshInFlight.current is true the code only sets refreshPending.current =
true and discards githubRefreshArgs; change this to also save the pending
refresh arguments (e.g., set a new or existing variable like
pendingGithubRefreshArgs = githubRefreshArgs or assign
refreshPendingArgs.current = githubRefreshArgs) whenever refreshPending.current
is set, and update the follow-up refresh runner (the logic that checks
refreshPending.current and clears githubRefreshArgs) to prefer and consume
pendingGithubRefreshArgs/current instead of always resetting to an unscoped
refresh; apply the same pattern where similar early-return logic appears (the
blocks around the refreshInFlight checks at lines referenced: 824-827, 831-842)
so queued targeted refreshes retain their scope.

In `@apps/desktop/src/renderer/components/run/RunPage.tsx`:
- Around line 884-909: The current onEvent handler nulls the entire
pendingRunLaunchRef when revealRuntimeTerminal(event.runtime) returns true,
which cancels remaining group targets; update the logic to only remove the
matched target from pendingRunLaunchRef.current (using the found targetIndex)
instead of setting it to null so sibling runtime events can still be processed
and cleaned up, and add a unit/integration test that emits runtime events for
both targets to assert whether the second process is revealed or only cleaned
up; focus changes around the pendingRunLaunchRef usage, the
revealRuntimeTerminal call, the targetIndex calculation, and the cleanup branch
that uses isActiveProcessStatus(event.runtime.status).

---

Nitpick comments:
In `@apps/desktop/src/main/services/chat/agentChatService.ts`:
- Around line 14829-14837: The fork branch currently checks
createdManaged.runtime?.kind === "claude" which can miss freshly-created Claude
sessions without a runtime; change the inner condition to check
createdManaged.session.provider === "claude" and call
ensureClaudeSessionRuntime(createdManaged) to create/return the runtime before
invoking resetClaudeQuerySession, setting
createdManaged.runtime.forkFromSdkSessionId and calling prewarmClaudeQuery, so
that mirrorClaudeSessionPointer and claudeBackgroundResumeSessionId are mirrored
but the reset/prewarm always run for Claude-provider sessions even if the
runtime did not yet exist.
- Around line 18883-18898: The inner null-check on short is redundant because
managed.claudeBackgroundJobShort is already tested in the outer if; remove the
inner if (short) and directly call await runClaudeBackgroundCliCommand(managed,
["stop", short], 15_000).catch(...). Keep the short local variable if you prefer
readability (or inline managed.claudeBackgroundJobShort into
runClaudeBackgroundCliCommand), and ensure the logger.warn,
markSessionIdleWithFreshCache(managed), and persistChatState(managed) behavior
remains unchanged.
- Around line 20600-20607: The current code calls
ensureClaudeSessionRuntime(managed) which may evict or allocate a new runtime
even when later guards bail; change the logic to avoid creating/mutating runtime
unless you will actually prewarm: first check for an existing runtime via
managed.runtime (or add a non-mutating accessor like
getClaudeSessionRuntimeIfExists/peekClaudeSessionRuntime) and only call
ensureClaudeSessionRuntime(managed) when those guards pass and you are about to
call prewarmClaudeQuery(managed); this prevents evictLeastRecentRuntime() and
assignment to managed.runtime happening unnecessarily and keeps
persistChatState(managed) paired only with true prewarm actions.

In `@apps/desktop/src/main/services/chat/claudeSlashCommandDiscovery.ts`:
- Around line 275-287: bundledSkillRoots currently uses __dirname unguarded
which can break in ESM contexts; add a guard at the top of bundledSkillRoots
(check typeof __dirname !== "string") and return a safe fallback (e.g., [] or
derive a path via fileURLToPath(import.meta.url)) to mirror the pattern used in
openCodeBinaryManager.ts / adeCliService.ts, and memoize the computed candidate
array (cache in a module-level variable) so repeated calls reuse the ~18
resolved paths instead of rebuilding them each time; reference bundledSkillRoots
and __dirname when making these changes.

In `@apps/desktop/src/main/services/prs/prService.ts`:
- Around line 6462-6464: The empty catch at the jobs fetch silently swallows
failures; update the catch in prs/prService.ts (the block that currently returns
an empty jobs array) to log a lightweight warning before returning, using the
repository/PR/run context available in scope (e.g., PR id, run id, commit SHA)
and include the caught error message/stack; use the existing logger instance in
this module (e.g., logger or processLogger) so the warning reads like "Failed to
fetch jobs for PR <prId>/run <runId> (sha=<sha>): <error>" and then return the
empty jobs array as before.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: adb2c5c4-b260-4f30-8cb8-bdc3cd79b149

📥 Commits

Reviewing files that changed from the base of the PR and between 3233e16 and 7bbba1d.

⛔ Files ignored due to path filters (30)
  • .github/workflows/prepare-release.yml is excluded by none and included by none
  • .github/workflows/release-core.yml is excluded by none and included by none
  • .github/workflows/release.yml is excluded by none and included by none
  • apps/ade-cli/src/cli.test.ts is excluded by none and included by none
  • apps/ade-cli/src/cli.ts is excluded by none and included by none
  • apps/ade-cli/src/multiProjectRpcServer.test.ts is excluded by none and included by none
  • apps/ade-cli/src/multiProjectRpcServer.ts is excluded by none and included by none
  • apps/ade-cli/src/tuiClient/__tests__/appPolling.test.tsx is excluded by none and included by none
  • apps/ade-cli/src/tuiClient/__tests__/connection.test.ts is excluded by none and included by none
  • apps/ade-cli/src/tuiClient/__tests__/project.test.ts is excluded by none and included by none
  • apps/ade-cli/src/tuiClient/__tests__/state.test.ts is excluded by none and included by none
  • apps/ade-cli/src/tuiClient/app.tsx is excluded by none and included by none
  • apps/ade-cli/src/tuiClient/cli.tsx is excluded by none and included by none
  • apps/ade-cli/src/tuiClient/connection.ts is excluded by none and included by none
  • apps/ade-cli/src/tuiClient/project.ts is excluded by none and included by none
  • apps/ade-cli/src/tuiClient/state.ts is excluded by none and included by none
  • apps/ade-cli/src/tuiClient/types.ts is excluded by none and included by none
  • apps/desktop/package.json is excluded by none and included by none
  • apps/desktop/resources/ade-cli-help.txt is excluded by none and included by none
  • apps/desktop/resources/agent-skills/ade-app-control/SKILL.md is excluded by none and included by none
  • apps/desktop/resources/agent-skills/ade-browser/SKILL.md is excluded by none and included by none
  • apps/desktop/resources/agent-skills/ade-cli-control-plane/SKILL.md is excluded by none and included by none
  • apps/desktop/resources/agent-skills/ade-cto-missions/SKILL.md is excluded by none and included by none
  • apps/desktop/resources/agent-skills/ade-ios-simulator/SKILL.md is excluded by none and included by none
  • apps/desktop/resources/agent-skills/ade-lanes-git/SKILL.md is excluded by none and included by none
  • apps/desktop/resources/agent-skills/ade-macos-vm/SKILL.md is excluded by none and included by none
  • apps/desktop/resources/agent-skills/ade-pr-workflows/SKILL.md is excluded by none and included by none
  • apps/desktop/resources/agent-skills/ade-proof-artifacts/SKILL.md is excluded by none and included by none
  • apps/desktop/scripts/validate-mac-artifacts.mjs is excluded by none and included by none
  • apps/desktop/scripts/validate-win-artifacts.mjs is excluded by none and included by none
📒 Files selected for processing (37)
  • apps/desktop/src/main/services/ai/tools/systemPrompt.test.ts
  • apps/desktop/src/main/services/chat/agentChatService.test.ts
  • apps/desktop/src/main/services/chat/agentChatService.ts
  • apps/desktop/src/main/services/chat/claudeSlashCommandDiscovery.test.ts
  • apps/desktop/src/main/services/chat/claudeSlashCommandDiscovery.ts
  • apps/desktop/src/main/services/files/fileService.test.ts
  • apps/desktop/src/main/services/files/fileService.ts
  • apps/desktop/src/main/services/memory/skillRegistryService.test.ts
  • apps/desktop/src/main/services/memory/skillRegistryService.ts
  • apps/desktop/src/main/services/prs/prService.test.ts
  • apps/desktop/src/main/services/prs/prService.ts
  • apps/desktop/src/main/services/remoteRuntime/remoteConnectionPool.test.ts
  • apps/desktop/src/main/services/remoteRuntime/remoteConnectionPool.ts
  • apps/desktop/src/renderer/components/app/LinearQuickViewButton.tsx
  • apps/desktop/src/renderer/components/app/TopBar.test.tsx
  • apps/desktop/src/renderer/components/app/TopBar.tsx
  • apps/desktop/src/renderer/components/chat/ChatAppControlPanel.test.tsx
  • apps/desktop/src/renderer/components/chat/ChatAppControlPanel.tsx
  • apps/desktop/src/renderer/components/chat/ChatIosSimulatorPanel.test.tsx
  • apps/desktop/src/renderer/components/chat/ChatIosSimulatorPanel.tsx
  • apps/desktop/src/renderer/components/files/FilesPage.test.tsx
  • apps/desktop/src/renderer/components/files/FilesPage.tsx
  • apps/desktop/src/renderer/components/lanes/LaneGitActionsPane.test.tsx
  • apps/desktop/src/renderer/components/lanes/LaneGitActionsPane.tsx
  • apps/desktop/src/renderer/components/prs/PRsPage.tsx
  • apps/desktop/src/renderer/components/prs/detail/PrDetailPane.issueResolver.test.tsx
  • apps/desktop/src/renderer/components/prs/detail/PrDetailPane.tsx
  • apps/desktop/src/renderer/components/prs/state/PrsContext.test.tsx
  • apps/desktop/src/renderer/components/prs/state/PrsContext.tsx
  • apps/desktop/src/renderer/components/prs/tabs/GitHubTab.test.tsx
  • apps/desktop/src/renderer/components/prs/tabs/GitHubTab.tsx
  • apps/desktop/src/renderer/components/run/RunPage.test.tsx
  • apps/desktop/src/renderer/components/run/RunPage.tsx
  • apps/desktop/src/renderer/components/terminals/WorkSidebar.test.tsx
  • apps/desktop/src/renderer/components/terminals/WorkSidebar.tsx
  • apps/desktop/src/renderer/components/usage/HeaderUsageControl.tsx
  • apps/desktop/src/shared/adeCliGuidance.ts

Comment thread apps/desktop/src/main/services/chat/agentChatService.test.ts Outdated
Comment thread apps/desktop/src/main/services/chat/agentChatService.ts
Comment thread apps/desktop/src/main/services/memory/skillRegistryService.ts
Comment thread apps/desktop/src/main/services/prs/prService.test.ts
Comment thread apps/desktop/src/renderer/components/chat/ChatIosSimulatorPanel.tsx Outdated
Comment thread apps/desktop/src/renderer/components/prs/detail/PrDetailPane.tsx
Comment thread apps/desktop/src/renderer/components/prs/detail/PrDetailPane.tsx Outdated
@arul28 arul28 merged commit 27a2d2c into main May 14, 2026
28 checks passed
@arul28 arul28 deleted the ade/final-audit-opt-3575a904 branch May 14, 2026 15:46
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