Skip to content

Fix Claude runtime resolution and lane delete warnings#290

Merged
arul28 merged 6 commits into
mainfrom
ade/fix-claude-runtime-lane-delete
May 12, 2026
Merged

Fix Claude runtime resolution and lane delete warnings#290
arul28 merged 6 commits into
mainfrom
ade/fix-claude-runtime-lane-delete

Conversation

@arul28
Copy link
Copy Markdown
Owner

@arul28 arul28 commented May 12, 2026

Summary

  • resolve packaged Claude native binaries and pass the executable path into Claude SDK sessions
  • filter Claude marketplace/cache plugin copies unless installed and enabled
  • treat allowed/warning rate-limit telemetry as non-error notices
  • let optional lane branch cleanup finish as warnings while completing lane deletion

Validation

  • npm --prefix apps/desktop run typecheck
  • npm --prefix apps/desktop run lint
  • npm --prefix apps/desktop run build
  • npm --prefix apps/web run typecheck
  • npm --prefix apps/web run build
  • npm --prefix apps/ade-cli run typecheck
  • npm --prefix apps/ade-cli run build
  • node scripts/validate-docs.mjs
  • focused desktop/chat/lane/Claude runtime tests
  • desktop test suite sharded 1/8 through 8/8
  • ADE CLI tests; reran stdio daemon test after runtime resolver fix

Note

Medium Risk
Medium risk: changes how the Claude runtime executable is resolved/passed into the SDK and alters lane deletion completion semantics, which could affect startup reliability and user-facing delete flows across platforms.

Overview
Improves Claude runtime reliability by centralizing executable resolution in claudeCodeExecutable.ts (env override → packaged bundled binary → auth/PATH fallbacks) and threading pathToClaudeCodeExecutable through Claude probes, smoke tests, and chat session startup, with clearer diagnostics when spawn errors look like false “native binary not found” failures.

Refines chat/system notice handling by adding severity/status to system_notice, emitting structured Claude/Codex rate-limit severities, and rendering non-blocking “allowed” rate-limit telemetry as non-error notices in both the CLI TUI and desktop UI.

Makes lane deletion more resilient by treating local/remote branch deletion as non-fatal steps, introducing warning step status plus completed_with_warnings, and updating the renderer progress/error surfaces to show actionable warnings while still completing local lane cleanup.

Tightens Claude plugin discovery to avoid loading cache/marketplace copies unless a plugin is installed and enabled via Claude settings/registry.

Reviewed by Cursor Bugbot for commit b991737. Configure here.

Summary by CodeRabbit

  • New Features

    • Added severity levels to rate-limit notifications for improved clarity
    • Enhanced lane deletion workflow to complete with warnings when non-critical steps fail
    • Improved plugin discovery to respect Claude's enabled plugins configuration
  • Bug Fixes

    • Refined system notice rendering for better error categorization
    • Enhanced executable resolution and error handling for runtime stability
    • Improved session-stale detection and recovery logic

Review Change Stack

Greptile Summary

This PR centralises Claude executable resolution into a single resolveClaudeCodeExecutable() helper (env override → ASAR-unpacked bundled binary → auth/PATH fallbacks) and threads pathToClaudeCodeExecutable through probe, startup, and session call sites. It also adds severity/status fields to system_notice events so allowed rate-limit telemetry renders as non-error notices, refines plugin discovery to skip uninstalled marketplace cache copies, and makes branch-cleanup steps non-fatal so lane deletion can finish as completed_with_warnings.

  • Claude runtime resolution (claudeCodeExecutable.ts, claudeRuntimeProbe.ts, agentChatService.ts, packagedRuntimeSmoke.ts): new resolveBundledClaudeCodeExecutable probes ASAR-unpacked paths first; false-negative "native binary not found" SDK errors are suppressed when the resolved binary already exists on disk.
  • Plugin discovery (claudeOutputStyles.ts): cache/marketplaces subdirectories are skipped by the normal walk; only plugins listed in installed_plugins.json with matching enabledPlugins settings are loaded from managed dirs.
  • Lane deletion resilience (laneService.ts, LanesPage.tsx, ManageLaneDialog.tsx): git_branch_delete and git_remote_branch_delete run with { fatal: false }, upgrade failed branch-cleanup to warning step status, and bubble the aggregate up to completed_with_warnings with amber UI treatment.

Confidence Score: 5/5

Safe to merge; the runtime resolution and lane-deletion changes are well-tested and follow a clear, incremental strategy.

All critical paths — bundled binary resolution with ASAR guard, session startup with pathToClaudeCodeExecutable, probe health check, and the non-fatal branch-cleanup flow — have accompanying unit tests covering the new behaviour. The two observations raised are edge cases in error-message suppression and stale banner state that don't affect core functionality.

No files require special attention beyond the two inline comments.

Important Files Changed

Filename Overview
apps/desktop/src/main/services/ai/claudeCodeExecutable.ts Adds resolveBundledClaudeCodeExecutable with ASAR-unpacked path probing and a resolvePackageFromRuntimeRoots fallback guarded by an ASAR path check; isExecutablePath exported for use in probe/suppression logic.
apps/desktop/src/main/services/chat/agentChatService.ts Threads pathToClaudeCodeExecutable into session startup; adds "missing" error suppression using a second unconstrained resolveClaudeCodeExecutable() call; rate-limit events now carry severity/status; stale-session check gains the "no conversation found" message variant.
apps/desktop/src/main/services/chat/claudeOutputStyles.ts Skips cache/marketplaces dirs at depth 0; adds discoverEnabledInstalledPluginPaths to load marketplace plugins from installed_plugins.json only when enabled in settings.
apps/desktop/src/main/services/lanes/laneService.ts runStep gains an optional fatal flag; branch-cleanup steps run with { fatal: false }; completed_with_warnings overall status surfaced when non-fatal failures accumulate.
apps/desktop/src/renderer/components/lanes/LanesPage.tsx isLaneDeleteProgressActive and lane refresh path now handle completed_with_warnings; warning messages tracked in a ref; clean-delete path uses a regex heuristic to clear stale delete-related error banners.
apps/desktop/src/renderer/components/lanes/ManageLaneDialog.tsx Adds completed_with_warnings amber tone to DeleteProgressStrip and warning step rendering with inline error message display in ProgressStepRow.
apps/desktop/src/renderer/components/chat/AgentChatMessageList.tsx Adds inferredSeverity logic for rate-limit notice styling; non-error rate-limit notices with detail render as compact inline chip without a collapsible card.
apps/desktop/src/main/services/ai/claudeRuntimeProbe.ts Passes pathToClaudeCodeExecutable to the probe query; adds a false-negative suppression path that enriches the error message with the resolved path when SDK reports "native binary not found" despite a resolvable executable.
apps/desktop/src/shared/types/lanes.ts Adds "warning" to LaneDeleteStepStatus and "completed_with_warnings" to LaneDeleteOverallStatus.
apps/desktop/src/shared/types/chat.ts Adds optional severity and status fields to the system_notice event union member.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[resolveClaudeCodeExecutable] --> B{CLAUDE_CODE_EXECUTABLE_PATH set?}
    B -- yes --> C[source: env]
    B -- no --> D{ASAR-unpacked binary exists?}
    D -- yes --> E[source: bundled]
    D -- no --> F{Auth-detected path?}
    F -- yes --> G[source: auth]
    F -- no --> H{Binary on PATH?}
    H -- yes --> I[source: path / common-dir]
    H -- no --> J[source: fallback-command]
    C & E & G & I --> K[pathToClaudeCodeExecutable passed to SDK]
    J --> K
    K --> L[Claude SDK session / probe]
    L --> M{SDK reports native binary not found?}
    M -- yes --> N{source != fallback-command AND isExecutablePath?}
    N -- yes --> O[Suppress false-negative, log warning]
    N -- no --> P[Surface error to user]
    M -- no --> Q[Normal event flow]
Loading

Comments Outside Diff (1)

  1. apps/desktop/src/renderer/components/chat/AgentChatMessageList.tsx, line 2046-2050 (link)

    P2 Severity inference relies on exact message-string patterns

    The backward-compat fallback for old events without severity derives the visual tone by regex-testing event.message. If the message wording changes in agentChatService.ts — even just capitalisation or a prefix change — old events replayed from the database will silently regress to "error" styling. Since severity is now always emitted by the service, consider treating events without an explicit severity on a rate_limit notice as "error" unconditionally, removing the fragile regex path entirely.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/desktop/src/renderer/components/chat/AgentChatMessageList.tsx
    Line: 2046-2050
    
    Comment:
    **Severity inference relies on exact message-string patterns**
    
    The backward-compat fallback for old events without `severity` derives the visual tone by regex-testing `event.message`. If the message wording changes in `agentChatService.ts` — even just capitalisation or a prefix change — old events replayed from the database will silently regress to "error" styling. Since `severity` is now always emitted by the service, consider treating events without an explicit `severity` on a `rate_limit` notice as `"error"` unconditionally, removing the fragile regex path entirely.
    
    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 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
apps/desktop/src/main/services/chat/agentChatService.ts:7011-7013
The "missing" error suppression resolves the executable with `process.env` (no args), while the session itself was started with `resolveClaudeCodeExecutable({ env: claudeEnv })`. When the session binary was found via auth detection (not a bundled or PATH binary), the suppression check skips `auth` lookup and falls back to PATH/common-dir. If the auth-detected binary isn't also on PATH, `resolved.source` would be `"fallback-command"` and the condition fails — the false-negative SDK error is shown to the user even though the session binary is valid. Passing the same env context used at startup keeps the two resolution calls consistent.

```suggestion
    if (managed.session.provider === "claude" && match.category === "missing") {
      const resolved = resolveClaudeCodeExecutable({ env: claudeEnv });
      if (resolved.source !== "fallback-command" && isExecutablePath(resolved.path)) {
```

### Issue 2 of 2
apps/desktop/src/renderer/components/lanes/LanesPage.tsx:827-831
When a lane completes cleanly (`completed`) during a batch delete, the functional update only clears `laneActionError` if the current message matches `/\bdelet(?:e|ed|ing)\b/`. An IPC-level exception from an earlier lane in the same batch (e.g. `"lane-x: connect ECONNREFUSED"`) would not match the regex and would therefore persist as a stale banner after all subsequent lanes succeed. Prefer explicitly clearing error state when there are no remaining warnings.

```suggestion
      } else {
        laneDeleteWarningMessagesRef.current.delete(laneId);
        const remainingWarnings = formatLaneDeleteWarningMessages(laneDeleteWarningMessagesRef.current);
        setLaneActionError(remainingWarnings);
      }
```

Reviews (4): Last reviewed commit: "Stabilize lane delete warning state" | Re-trigger Greptile

@vercel
Copy link
Copy Markdown

vercel Bot commented May 12, 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 12, 2026 6:50pm

@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented May 12, 2026

@cursor review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

📝 Walkthrough

Walkthrough

This PR extends system notice rendering with explicit severity fields, implements bundled Claude Code executable detection, enhances rate-limit event severity computation, introduces non-fatal lane deletion warnings, and integrates Claude plugin settings-based discovery and filtering.

Changes

System Notice Severity, Executable Resolution, and Rate-Limit Handling

Layer / File(s) Summary
System notice severity type definition and TUI rendering
apps/desktop/src/shared/types/chat.ts, apps/ade-cli/src/tuiClient/__tests__/format.test.ts, apps/ade-cli/src/tuiClient/format.ts
The AgentChatEvent union's system_notice variant gains optional severity and status fields. TUI tone selection now checks explicit severity === "error" before falling back to notice-kind heuristics for error, thread_error, provider_health, and rate_limit kinds.
Bundled Claude Code executable detection and resolution
apps/desktop/src/main/services/ai/claudeCodeExecutable.ts, apps/desktop/src/main/services/ai/claudeCodeExecutable.test.ts
New isExecutablePath validator and bundled binary discovery logic search expected Electron unpack directories and package.json locations. resolveClaudeCodeExecutable now accepts resourcesPath, platform, arch, and packageResolver parameters and returns "bundled" source when a packaged binary is found.
Smoke test and runtime probe executable integration
apps/desktop/src/main/packagedRuntimeSmoke.ts, apps/desktop/src/main/services/ai/claudeRuntimeProbe.ts, apps/desktop/src/main/services/ai/claudeRuntimeProbe.test.ts
Smoke test resolves Claude executable and includes both path and source in stdout JSON. Runtime probe resolves executable and passes path via pathToClaudeCodeExecutable option; error handling distinguishes missing-binary false negatives and formats runtime-failed messages with the resolved path.
AI integration service bundled binary detection
apps/desktop/src/main/services/ai/aiIntegrationService.ts
Refactors resolveBundledClaudeBinary to delegate to resolveClaudeCodeExecutable({ env: { PATH: "" } }) and map the returned source.
Rate-limit event severity computation and system notice emission
apps/desktop/src/main/services/chat/agentChatService.ts
Claude rate-limit handling normalizes status codes, maps them to severity (info/warning/error), and emits system notices with explicit severity and status fields. Also expands stale-session error patterns, resolves Claude executable for session setup, and adds debug logging.
Test updates for executable resolution and rate-limit handling
apps/desktop/src/main/services/chat/agentChatService.test.ts
Adds home directory isolation, mocks resolveClaudeCodeExecutable, strengthens rate-limit test assertions (severity/status/executable path), and updates runtime probe test expectations.
Renderer system notice severity-based display logic
apps/desktop/src/renderer/components/chat/AgentChatMessageList.tsx, apps/desktop/src/renderer/components/chat/AgentChatMessageList.test.tsx
Infers effective severity from event fields and notice-kind patterns. Selects style based on severity for rate-limit messages and renders compact inline details for non-error rate-limit notices; includes new test cases for allowed vs. blocking rate-limits.

Lane Deletion with Non-Fatal Warnings

Layer / File(s) Summary
Lane deletion status types with warning support
apps/desktop/src/shared/types/lanes.ts
LaneDeleteStepStatus gains "warning" and LaneDeleteOverallStatus gains "completed_with_warnings" discriminators.
Lane service deletion workflow with non-fatal steps
apps/desktop/src/main/services/lanes/laneService.ts, apps/desktop/src/main/services/lanes/laneService.test.ts
Adds nonFatalFailures tracking and extends runStep to accept a fatal flag. Branch-deletion steps (git_branch_delete, git_remote_branch_delete) are marked non-fatal, allowing deletion to continue and finalize as completed_with_warnings when non-fatal failures occur.
LanesPage UI state and event handling for lane warnings
apps/desktop/src/renderer/components/lanes/LanesPage.tsx
Adds per-lane warning message tracking via ref, extends active-state detection to include completed_with_warnings, formats and accumulates warning messages across concurrent deletes, and widens error banner with truncation and dismiss logic.
ManageLaneDialog warning display components
apps/desktop/src/renderer/components/lanes/ManageLaneDialog.tsx
DeleteProgressStrip recognizes completed_with_warnings with amber styling and post-delete message. ProgressStepRow supports warning step status with amber icon/tone and renders truncated error messages with tooltips.

Claude Plugin Discovery with Settings Integration

Layer / File(s) Summary
Plugin discovery settings integration and filtering
apps/desktop/src/main/services/chat/claudeOutputStyles.ts
Introduces CLAUDE_MANAGED_PLUGIN_DIRS to skip managed directories, extends settings/plugin-entry types with optional fields (enabledPlugins, path, scope, projectPath), adds JSON/path utilities (maybeRecord, readJsonObject, pathsOverlap), and implements a pipeline to discover enabled installed plugins from precedence-ordered settings and registry entries with scope/path-overlap validation.
Plugin discovery test coverage for settings and scope filtering
apps/desktop/src/main/services/chat/claudeOutputStyles.test.ts
Adds cases for marketplace cache exclusion, manually-placed user plugins, and enabled-plugin filtering via settings.json and installed_plugins.json with enablement checks.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • arul28/ADE#288: Changes system_notice tone/rendering logic in format.ts and related TUI components, directly related to this PR's severity-based tone inference updates.
  • arul28/ADE#287: Modifies system_notice rendering for rate_limit and other notice kinds across TUI and renderer, related to severity/tone heuristics introduced here.
  • arul28/ADE#87: Updates system_notice rendering and AgentChatEvent types, related to the addition of severity and status fields.

Suggested labels

desktop

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.63% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix Claude runtime resolution and lane delete warnings' accurately summarizes the two main changes in the PR: improvements to Claude runtime executable resolution and making lane deletion resilient to non-fatal branch cleanup failures.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 ade/fix-claude-runtime-lane-delete

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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.

@capy-ai
Copy link
Copy Markdown

capy-ai Bot commented May 12, 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.

Comment thread apps/desktop/src/main/services/chat/claudeOutputStyles.ts Outdated
Comment thread apps/desktop/src/renderer/components/chat/AgentChatMessageList.tsx
Comment thread apps/desktop/src/main/services/chat/claudeOutputStyles.ts
Comment thread apps/desktop/src/renderer/components/lanes/LanesPage.tsx Outdated
@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented May 12, 2026

@cursor review

Comment thread apps/desktop/src/renderer/components/lanes/LanesPage.tsx Outdated
Comment thread apps/desktop/src/renderer/components/chat/AgentChatMessageList.tsx
@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented May 12, 2026

@cursor review

@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented May 12, 2026

@cursor review

Comment thread apps/desktop/src/renderer/components/lanes/LanesPage.tsx
Comment thread apps/desktop/src/main/services/chat/agentChatService.test.ts
@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented May 12, 2026

@cursor review

Comment thread apps/desktop/src/main/services/ai/claudeRuntimeProbe.ts
Comment thread apps/desktop/src/renderer/components/lanes/LanesPage.tsx
Comment thread apps/desktop/src/renderer/components/lanes/LanesPage.tsx
@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented May 12, 2026

@cursor review

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b991737. Configure here.

laneDeleteWarningMessagesRef.current.delete(laneId);
const remainingWarnings = formatLaneDeleteWarningMessages(laneDeleteWarningMessagesRef.current);
setLaneActionError((current) => remainingWarnings ?? (current && /\bdelet(?:e|ed|ing)\b/i.test(current) ? null : current));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale error banner persists after clean lane deletion

Medium Severity

The completed branch clears laneActionError only when the current error matches /\bdelet(?:e|ed|ing)\b/i. A prior non-delete laneActionError (e.g. from an archive or adopt action) will persist indefinitely after a subsequent clean lane deletion, since the regex won't match. The old code unconditionally called setLaneActionError(null), which always cleared stale banners. The new conditional preserves unrelated errors that the user has no way to clear except by manually dismissing the chip.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b991737. Configure here.

});
return event;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Error decorator resolves executable without session environment

Low Severity

decorateAgentCliError calls resolveClaudeCodeExecutable() with no arguments (defaulting to process.env), while the session's SDK was started with resolveClaudeCodeExecutable({ env: claudeEnv }). If claudeEnv differs from process.env (e.g., a custom CLAUDE_CODE_EXECUTABLE_PATH injected by buildAgentRuntimeEnv), the suppression check may reference a different path than what the SDK actually attempted to spawn, potentially suppressing or failing to suppress the agentCli decoration incorrectly.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b991737. Configure here.

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: 3

🧹 Nitpick comments (4)
apps/desktop/src/main/services/lanes/laneService.ts (1)

3548-3585: ⚡ Quick win

Consider adding inline comments to clarify fatal vs non-fatal behavior.

The implementation is correct, but the distinction between fatal and non-fatal step failures could be clearer for future maintainers. Consider adding a brief comment explaining:

  • Fatal failures (default) stop execution and throw
  • Non-fatal failures log warnings and allow execution to continue
  • The status is set to "warning" instead of "failed" for non-fatal failures
📝 Example inline documentation
 const runStep = async (
   name: LaneDeleteStepName,
   work: () => Promise<{ detail?: string } | void>,
   options?: { fatal?: boolean },
 ): Promise<void> => {
   const step = findStep(name);
   if (!step) return;
+  // Fatal failures (default) stop execution; non-fatal failures log warnings and continue.
   const fatal = options?.fatal !== false;
   step.status = "running";
🤖 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/lanes/laneService.ts` around lines 3548 -
3585, Add short inline comments inside the runStep function (around the
try/catch and where fatal is computed) explaining the fatal vs non-fatal
behavior: note that fatal defaults to true (options?.fatal !== false), fatal
failures set step.status = "failed", set errorMessage, broadcast and rethrow to
stop deletion; non-fatal failures set step.status = "warning", push to
nonFatalFailures, log a warning via
logger.warn("lane.delete.non_fatal_step_failed", ...),
broadcastDeleteEvent(progress), and return to allow subsequent steps to
continue. Place these comments near the declarations of fatal, the catch block,
and where status is assigned so future maintainers can quickly see the intent.
apps/desktop/src/main/services/ai/claudeRuntimeProbe.test.ts (1)

67-71: ⚡ Quick win

Strengthen executable-path assertion to reject empty strings.

expect.any(String) still passes "", which can hide resolver regressions.

Diff suggestion
-        pathToClaudeCodeExecutable: expect.any(String),
+        pathToClaudeCodeExecutable: expect.stringMatching(/.+/),
🤖 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/ai/claudeRuntimeProbe.test.ts` around lines 67
- 71, The test currently uses expect.any(String) for pathToClaudeCodeExecutable
which allows empty strings; update the assertion in the mockState.query
expectation for pathToClaudeCodeExecutable to assert a non-empty string (for
example use expect.stringMatching(/\S/) or expect.not.toEqual('')) so empty
values fail and regressions are caught. Target the expectation inside
claudeRuntimeProbe.test.ts where mockState.query is asserted.
apps/desktop/src/main/services/chat/agentChatService.ts (1)

7011-7022: ⚡ Quick win

Consider caching resolveClaudeCodeExecutable() result if "missing" CLI errors occur frequently.

The function performs filesystem and PATH scanning operations without caching. Similar utilities in the codebase (git.ts, openCodeBinaryManager.ts) cache results to avoid repeated resolution work. If this error suppression path triggers multiple times in a session, caching the resolved path would improve efficiency.

🤖 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 7011 -
7022, The suppression path calls resolveClaudeCodeExecutable() repeatedly; cache
its result and reuse it to avoid repeated filesystem/PATH scans. Add a small
module-level cache (e.g., cachedResolvedClaudeExecutable + cachedResolvedAt or a
Map keyed by managed.session.id) and read from it in the if-block before calling
resolveClaudeCodeExecutable(); only call resolveClaudeCodeExecutable() when
there is no cached entry or the cache is stale, then store the returned object
(path and source). Use the cached value in the isExecutablePath(resolved.path)
check and when logging via logger.warn, and ensure the cache can be invalidated
when environment/installation changes if necessary.
apps/desktop/src/main/services/chat/claudeOutputStyles.ts (1)

226-230: 💤 Low value

Verify enablement predicate matches Claude SDK specification.

The function treats any value except false, null, undefined, or {enabled: false} as enabled. This is permissive and would accept numbers, strings, empty objects, etc. as "enabled."

While this may be intentional to support both boolean ("plugin-id": true) and object ("plugin-id": {enabled: true, config: {...}}) formats, please verify this matches Claude SDK's actual plugin enablement semantics.

Claude SDK plugin settings enablement format specification
🤖 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/claudeOutputStyles.ts` around lines 226 -
230, The predicate is too permissive: update isEnabledPluginSetting to only
return true for explicit boolean true or for objects where
maybeRecord(value)?.enabled is true (or enabled is missing only if the Claude
SDK allows implicit enablement—confirm spec); reject numbers, strings, empty
objects, and other types. Modify the logic in isEnabledPluginSetting (and use
maybeRecord) so it first checks typeof value === "boolean" && value === true,
then checks for a record where enabled === true (or allowed missing enabled per
SDK), otherwise return false.
🤖 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/ai/claudeCodeExecutable.ts`:
- Around line 27-30: isExecutablePath currently checks executability using
process.platform which breaks cross-platform resolution in
resolveBundledClaudeCodeExecutable(platform); update isExecutablePath to accept
a platform parameter (e.g., isExecutablePath(candidatePath: string, platform?:
string)) and use that platform value (fallback to process.platform if undefined)
when deciding Windows vs POSIX executable checks, then update calls from
resolveBundledClaudeCodeExecutable and any other callers (including the block
referenced around lines 80-92) to pass the platform argument so .exe files on
Windows bundles are recognized even when running on macOS/Linux.

In `@apps/desktop/src/renderer/components/chat/AgentChatMessageList.tsx`:
- Around line 2536-2547: The inferredSeverity logic should first use
event.status (when present) to classify rate-limit notices before falling back
to message regexes; modify the inferredSeverity calculation in
AgentChatMessageList (and the similar block at the other occurrence) so that
when event.noticeKind === "rate_limit" and event.status exists you map known
statuses to severity (e.g., an allowed/ok status => "info", a warning status =>
"warning", failure/blocked => "error") and return that value prior to running
the regex tests on event.message; keep the existing fallbacks (event.severity,
noticeKind checks for error/warning, and final default "info") intact and
reference the same symbols (inferredSeverity, event.status, event.noticeKind,
event.message) so the change is localized and consistent across both locations.

In `@apps/desktop/src/renderer/components/lanes/LanesPage.tsx`:
- Line 331: laneDeleteWarningMessagesRef (a useRef Map) retains entries across
project switches and unmounts causing stale messages to leak; clear it when the
surrounding project/context changes and on component unmount. Add logic that
calls laneDeleteWarningMessagesRef.current.clear() inside an effect that depends
on the project/context identifier (and/or in the component's cleanup) so the Map
is reset on project switch and when the component unmounts; reference the
existing laneDeleteWarningMessagesRef symbol to place the clear() calls.

---

Nitpick comments:
In `@apps/desktop/src/main/services/ai/claudeRuntimeProbe.test.ts`:
- Around line 67-71: The test currently uses expect.any(String) for
pathToClaudeCodeExecutable which allows empty strings; update the assertion in
the mockState.query expectation for pathToClaudeCodeExecutable to assert a
non-empty string (for example use expect.stringMatching(/\S/) or
expect.not.toEqual('')) so empty values fail and regressions are caught. Target
the expectation inside claudeRuntimeProbe.test.ts where mockState.query is
asserted.

In `@apps/desktop/src/main/services/chat/agentChatService.ts`:
- Around line 7011-7022: The suppression path calls
resolveClaudeCodeExecutable() repeatedly; cache its result and reuse it to avoid
repeated filesystem/PATH scans. Add a small module-level cache (e.g.,
cachedResolvedClaudeExecutable + cachedResolvedAt or a Map keyed by
managed.session.id) and read from it in the if-block before calling
resolveClaudeCodeExecutable(); only call resolveClaudeCodeExecutable() when
there is no cached entry or the cache is stale, then store the returned object
(path and source). Use the cached value in the isExecutablePath(resolved.path)
check and when logging via logger.warn, and ensure the cache can be invalidated
when environment/installation changes if necessary.

In `@apps/desktop/src/main/services/chat/claudeOutputStyles.ts`:
- Around line 226-230: The predicate is too permissive: update
isEnabledPluginSetting to only return true for explicit boolean true or for
objects where maybeRecord(value)?.enabled is true (or enabled is missing only if
the Claude SDK allows implicit enablement—confirm spec); reject numbers,
strings, empty objects, and other types. Modify the logic in
isEnabledPluginSetting (and use maybeRecord) so it first checks typeof value ===
"boolean" && value === true, then checks for a record where enabled === true (or
allowed missing enabled per SDK), otherwise return false.

In `@apps/desktop/src/main/services/lanes/laneService.ts`:
- Around line 3548-3585: Add short inline comments inside the runStep function
(around the try/catch and where fatal is computed) explaining the fatal vs
non-fatal behavior: note that fatal defaults to true (options?.fatal !== false),
fatal failures set step.status = "failed", set errorMessage, broadcast and
rethrow to stop deletion; non-fatal failures set step.status = "warning", push
to nonFatalFailures, log a warning via
logger.warn("lane.delete.non_fatal_step_failed", ...),
broadcastDeleteEvent(progress), and return to allow subsequent steps to
continue. Place these comments near the declarations of fatal, the catch block,
and where status is assigned so future maintainers can quickly see the intent.
🪄 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: 18bbec48-f9fa-4856-bcdc-b2d8648ec0bf

📥 Commits

Reviewing files that changed from the base of the PR and between 7ccd831 and b991737.

⛔ Files ignored due to path filters (2)
  • docs/features/chat/README.md is excluded by !docs/**
  • docs/features/lanes/README.md is excluded by !docs/**
📒 Files selected for processing (20)
  • apps/ade-cli/src/tuiClient/__tests__/format.test.ts
  • apps/ade-cli/src/tuiClient/format.ts
  • apps/desktop/src/main/packagedRuntimeSmoke.ts
  • apps/desktop/src/main/services/ai/aiIntegrationService.ts
  • apps/desktop/src/main/services/ai/claudeCodeExecutable.test.ts
  • apps/desktop/src/main/services/ai/claudeCodeExecutable.ts
  • apps/desktop/src/main/services/ai/claudeRuntimeProbe.test.ts
  • apps/desktop/src/main/services/ai/claudeRuntimeProbe.ts
  • apps/desktop/src/main/services/chat/agentChatService.test.ts
  • apps/desktop/src/main/services/chat/agentChatService.ts
  • apps/desktop/src/main/services/chat/claudeOutputStyles.test.ts
  • apps/desktop/src/main/services/chat/claudeOutputStyles.ts
  • apps/desktop/src/main/services/lanes/laneService.test.ts
  • apps/desktop/src/main/services/lanes/laneService.ts
  • apps/desktop/src/renderer/components/chat/AgentChatMessageList.test.tsx
  • apps/desktop/src/renderer/components/chat/AgentChatMessageList.tsx
  • apps/desktop/src/renderer/components/lanes/LanesPage.tsx
  • apps/desktop/src/renderer/components/lanes/ManageLaneDialog.tsx
  • apps/desktop/src/shared/types/chat.ts
  • apps/desktop/src/shared/types/lanes.ts

Comment on lines +27 to +30
export function isExecutablePath(candidatePath: string): boolean {
try {
const stat = fs.statSync(candidatePath);
return stat.isFile() && (process.platform === "win32" || (stat.mode & 0o111) !== 0);
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 | ⚡ Quick win

Use the target platform when checking executability.

resolveBundledClaudeCodeExecutable() accepts platform, but isExecutablePath() still keys off process.platform. That makes cross-platform resolution false-negative for Windows bundles when this code runs on macOS/Linux, so a valid packaged .exe can be skipped and the resolver falls back to auth/PATH instead.

Suggested fix
-export function isExecutablePath(candidatePath: string): boolean {
+export function isExecutablePath(
+  candidatePath: string,
+  platform: NodeJS.Platform = process.platform,
+): boolean {
   try {
     const stat = fs.statSync(candidatePath);
-    return stat.isFile() && (process.platform === "win32" || (stat.mode & 0o111) !== 0);
+    return stat.isFile() && (platform === "win32" || (stat.mode & 0o111) !== 0);
   } catch {
     return false;
   }
 }
@@
   for (const candidate of candidates) {
-    if (isExecutablePath(candidate)) return candidate;
+    if (isExecutablePath(candidate, platform)) return candidate;
   }
@@
-    return isExecutablePath(binaryPath) ? binaryPath : null;
+    return isExecutablePath(binaryPath, platform) ? binaryPath : null;

Also applies to: 80-92

🤖 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/ai/claudeCodeExecutable.ts` around lines 27 -
30, isExecutablePath currently checks executability using process.platform which
breaks cross-platform resolution in
resolveBundledClaudeCodeExecutable(platform); update isExecutablePath to accept
a platform parameter (e.g., isExecutablePath(candidatePath: string, platform?:
string)) and use that platform value (fallback to process.platform if undefined)
when deciding Windows vs POSIX executable checks, then update calls from
resolveBundledClaudeCodeExecutable and any other callers (including the block
referenced around lines 80-92) to pass the platform argument so .exe files on
Windows bundles are recognized even when running on macOS/Linux.

Comment on lines +2536 to +2547
const inferredSeverity = event.severity
?? (event.noticeKind === "rate_limit" && /^Claude rate limit allowed warning/i.test(event.message) ? "warning" as const : undefined)
?? (event.noticeKind === "rate_limit" && /^Claude rate limit allowed/i.test(event.message) ? "info" as const : undefined)
?? (
event.noticeKind === "rate_limit"
|| event.noticeKind === "error"
|| event.noticeKind === "thread_error"
|| event.noticeKind === "provider_health"
? "error" as const
: undefined
)
?? (event.noticeKind === "warning" ? "warning" as const : "info" as const);
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 | ⚡ Quick win

Use event.status to classify rate-limit severity before message regexes.

Line 2536 currently relies on severity or message text patterns. If upstream provides status (but no severity), a non-blocking rate-limit notice can still render as an error when message text changes.

Suggested fix
+    const rateLimitStatus = event.noticeKind === "rate_limit"
+      ? String(event.status ?? "").trim().toLowerCase()
+      : "";
     const inferredSeverity = event.severity
+      ?? (
+        event.noticeKind === "rate_limit" && /^allowed(?:_|$)/.test(rateLimitStatus)
+          ? (rateLimitStatus.includes("warning") ? "warning" as const : "info" as const)
+          : undefined
+      )
       ?? (event.noticeKind === "rate_limit" && /^Claude rate limit allowed warning/i.test(event.message) ? "warning" as const : undefined)
       ?? (event.noticeKind === "rate_limit" && /^Claude rate limit allowed/i.test(event.message) ? "info" as const : undefined)
       ?? (
         event.noticeKind === "rate_limit"
           || event.noticeKind === "error"
           || event.noticeKind === "thread_error"
           || event.noticeKind === "provider_health"
           ? "error" as const
           : undefined
       )
       ?? (event.noticeKind === "warning" ? "warning" as const : "info" as const);

As per coding guidelines **/*.{ts,tsx}: Keep IPC contracts, preload types, shared types, and renderer usage in sync whenever an interface changes in TypeScript.

Also applies to: 2570-2589

🤖 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/chat/AgentChatMessageList.tsx` around
lines 2536 - 2547, The inferredSeverity logic should first use event.status
(when present) to classify rate-limit notices before falling back to message
regexes; modify the inferredSeverity calculation in AgentChatMessageList (and
the similar block at the other occurrence) so that when event.noticeKind ===
"rate_limit" and event.status exists you map known statuses to severity (e.g.,
an allowed/ok status => "info", a warning status => "warning", failure/blocked
=> "error") and return that value prior to running the regex tests on
event.message; keep the existing fallbacks (event.severity, noticeKind checks
for error/warning, and final default "info") intact and reference the same
symbols (inferredSeverity, event.status, event.noticeKind, event.message) so the
change is localized and consistent across both locations.

const [laneActionError, setLaneActionError] = useState<string | null>(null);
const [laneActionKind, setLaneActionKind] = useState<"delete" | "archive" | "adopt" | null>(null);
const [deleteProgressByLaneId, setDeleteProgressByLaneId] = useState<Record<string, LaneDeleteProgress>>({});
const laneDeleteWarningMessagesRef = useRef<Map<string, string>>(new Map());
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 | ⚡ Quick win

Clear stale delete-warning state on project/context reset (Line 331).

laneDeleteWarningMessagesRef survives project switches and unmount cleanup paths, so old warning text can leak into later delete warning aggregation.

💡 Proposed fix
  useEffect(() => {
    completedLaneDeleteRefreshesRef.current.clear();
    pendingLaneDeleteRefreshIdsRef.current.clear();
+   laneDeleteWarningMessagesRef.current.clear();
+   setLaneActionError((current) =>
+     current && /\bdelet(?:e|ed|ing)\b/i.test(current) ? null : current
+   );
    if (laneDeleteRefreshTimerRef.current != null) {
      window.clearTimeout(laneDeleteRefreshTimerRef.current);
      laneDeleteRefreshTimerRef.current = null;
    }
    setDeleteProgressByLaneId({});
  }, [project?.rootPath]);

  useEffect(() => {
    const pendingLaneDeleteRefreshIds = pendingLaneDeleteRefreshIdsRef.current;
    return () => {
+     laneDeleteWarningMessagesRef.current.clear();
      for (const timer of chipTimersRef.current.values()) window.clearTimeout(timer);
      chipTimersRef.current.clear();
      if (laneDeleteRefreshTimerRef.current != null) {
        window.clearTimeout(laneDeleteRefreshTimerRef.current);
        laneDeleteRefreshTimerRef.current = null;
      }
      pendingLaneDeleteRefreshIds.clear();
    };
  }, []);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const laneDeleteWarningMessagesRef = useRef<Map<string, string>>(new Map());
useEffect(() => {
completedLaneDeleteRefreshesRef.current.clear();
pendingLaneDeleteRefreshIdsRef.current.clear();
laneDeleteWarningMessagesRef.current.clear();
setLaneActionError((current) =>
current && /\bdelet(?:e|ed|ing)\b/i.test(current) ? null : current
);
if (laneDeleteRefreshTimerRef.current != null) {
window.clearTimeout(laneDeleteRefreshTimerRef.current);
laneDeleteRefreshTimerRef.current = null;
}
setDeleteProgressByLaneId({});
}, [project?.rootPath]);
useEffect(() => {
const pendingLaneDeleteRefreshIds = pendingLaneDeleteRefreshIdsRef.current;
return () => {
laneDeleteWarningMessagesRef.current.clear();
for (const timer of chipTimersRef.current.values()) window.clearTimeout(timer);
chipTimersRef.current.clear();
if (laneDeleteRefreshTimerRef.current != null) {
window.clearTimeout(laneDeleteRefreshTimerRef.current);
laneDeleteRefreshTimerRef.current = null;
}
pendingLaneDeleteRefreshIds.clear();
};
}, []);
🤖 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/lanes/LanesPage.tsx` at line 331,
laneDeleteWarningMessagesRef (a useRef Map) retains entries across project
switches and unmounts causing stale messages to leak; clear it when the
surrounding project/context changes and on component unmount. Add logic that
calls laneDeleteWarningMessagesRef.current.clear() inside an effect that depends
on the project/context identifier (and/or in the component's cleanup) so the Map
is reset on project switch and when the component unmounts; reference the
existing laneDeleteWarningMessagesRef symbol to place the clear() calls.

@arul28 arul28 merged commit 8ff6171 into main May 12, 2026
29 checks passed
@arul28 arul28 deleted the ade/fix-claude-runtime-lane-delete branch May 12, 2026 19:08
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.

1 participant