Skip to content

Fix lost auto-create lane / background launch on project switch#607

Open
arul28 wants to merge 1 commit into
mainfrom
ade/investigate-lost-auto-lane-578b6503
Open

Fix lost auto-create lane / background launch on project switch#607
arul28 wants to merge 1 commit into
mainfrom
ade/investigate-lost-auto-lane-578b6503

Conversation

@arul28

@arul28 arul28 commented Jun 18, 2026

Copy link
Copy Markdown
Owner

Problem

An in-flight draft launch (auto-create-lane or start in background) from the new-chat composer could vanish with no trace when the user switched projects mid-launch — most reliably between two remote projects. No new lane, no chat, no error: "as if it crashed."

Two root causes:

  1. Job state lived in the per-project renderer store. Switching to another remote project tears down the originating project's scoped store entirely (only the active remote surface stays mounted, App.tsx), dropping the in-flight job.
  2. The launch routed its mutating IPC via the mutable global project binding. A mid-launch switch could create a lane/session in the wrong project, or throw PROJECT_SWITCHING, with the failure swallowed (the originating pane had unmounted).

Fix

  • Hoist draft-launch job state to the root store (useRootAppStore / rootAppStoreApi) so it survives surface teardown and re-surfaces (or auto-opens / shows a Restorable failure) on return.
  • Capture the originating project binding at launch and abort before any mutating IPC (assertLaunchProjectActive) if the active project drifted — no lane/session created in the wrong project.
  • Pin rollback deletes (lanes.delete / agentChat.delete) to the originating project via a new additive callPinnedRuntimeAction, so cleanup never misroutes.
  • 90s launch timeout (withDraftLaunchTimeout) so a dropped remote connection can't wedge a job in a non-terminal state.

Tests / docs

  • New draftLaunchJobs.test.ts (timeout helper + prune invariant) and two behavioral regressions in AgentChatPane.test.tsx (drift-abort, pinned rollback).
  • Internal docs updated (chat composer, remote-runtime routing). Mobile/CLI/TUI verified no parity changes needed (pin is preload-internal, never crosses the sync wire).

🤖 Generated with Claude Code

Greptile Summary

  • Moves draft launch job state into the root app store so chat/CLI draft launches can survive project switches.
  • Captures project binding context for draft launch rollback paths and adds project drift checks around launch mutations.
  • Adds timeout handling for draft launch lane/session setup, plus tests and chat documentation for the new recovery behavior.

Confidence Score: 1/5

The draft-launch changes still have multiple project-binding race paths that can create, fetch, clean up, or restore state in the wrong runtime.

Several affected paths involve irreversible mutations across mutable project bindings, and focused runtime checks confirmed multiple failure modes around timeout continuation, cleanup, branch fetch drift, and remote job scoping.

apps/desktop/src/renderer/components/chat/AgentChatPane.tsx and the draft launch job scoping logic need attention before merging.

T-Rex T-Rex Logs

What T-Rex did

  • I added a focused Vitest reproduction to AgentChatPane.test.tsx that switches the mocked active project after lane creation and records which project agentChat.create observes.
  • I attempted to run the focused Vitest reproduction, but the app code could not execute because Node v20.9.0 lacks node:util.styleText required by the toolchain.
  • I attempted to restore local test tooling with npm --prefix apps/desktop install, but it was blocked by the same Node incompatibility and an ENOTEMPTY rename failure in node_modules.
  • I executed a focused Node harness modeling the draft lane launch timeout path and observed that the renderer-side wait was rejected with targetLane still null and the job marked failed.
  • After releasing the delayed lane naming step, the original unresolved lane setup continued and invoked window.ade.lanes.create after the failure.
  • I ran a focused Node harness modeling createSessionForLane cleanup with mutable preload project binding, created chat under project A, switched the active project to B during orchestration.runCreate failure, and recorded cleanup delete resolving against project B for the same session id.
  • I ran a focused Node harness modeling resolveDraftLaunchLane calling fetchNewLaneBaseBranches after switching the mutable active project from project-A to project-B, and the API trace showed fetch and listBranches returning 200 OK for project-B.
  • I executed a focused harness that mirrors selectActiveProjectRoot and the draft launch jobs scope for two remote bindings with the same rootPath, and observed both remotes computing the same draft-launch-jobs key with a ready remote A job visible under remote B's key.
  • I captured artifacts showing the difference in draft-launch root-store behavior before and after, where the base AgentChatPane used setDraftLaunchJobsInStore and the updated root-store state kept the job visible after teardown.
  • I prepared proof files for draft-launch timeout states before and after, noting that direct Vitest installation/execution was blocked and that I used deterministic Node harnesses that execute the same helper logic.
  • I assembled harness artifacts that include the exact harness source, commands, working directories, and captured stdout/stderr for attempted base/head runs, and noted that this validation targets a preload IPC contract rather than an HTTP endpoint, making the result inconclusive.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (3)

  1. apps/desktop/src/renderer/components/chat/AgentChatPane.tsx, line 6605-6611 (link)

    P1 Orchestration cleanup unpinned

    This cleanup path still calls agentChat.delete without the launch pin. During a draft launch, createSessionForLane can create the origin-project session, the user can switch projects, and then orchestration.runCreate can fail. This unpinned delete resolves against the newly active project, leaving the created lead chat behind in the origin project, or deleting a same-id session in the wrong project. Thread the launch pin into createSessionForLane so this cleanup uses the same pinned delete path.

    Artifacts

    Repro: focused cleanup project switch harness

    • Contains supporting evidence from the run (text/javascript; charset=utf-8).

    Repro: harness output with structured call trace

    • Keeps the command output available without making the summary code-heavy.

    Repro: generated focused Vitest component test

    • Contains supporting evidence from the run (text/tsx; charset=utf-8).

    Repro: attempted Vitest execution output

    • Keeps the command output available without making the summary code-heavy.

    View artifacts

    T-Rex Ran code and verified through T-Rex

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/desktop/src/renderer/components/chat/AgentChatPane.tsx
    Line: 6605-6611
    
    Comment:
    **Orchestration cleanup unpinned**
    
    This cleanup path still calls `agentChat.delete` without the launch pin. During a draft launch, `createSessionForLane` can create the origin-project session, the user can switch projects, and then `orchestration.runCreate` can fail. This unpinned delete resolves against the newly active project, leaving the created lead chat behind in the origin project, or deleting a same-id session in the wrong project. Thread the launch pin into `createSessionForLane` so this cleanup uses the same pinned delete path.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code

  2. apps/desktop/src/renderer/components/chat/AgentChatPane.tsx, line 6915-6919 (link)

    P1 Project drift before fetch

    The active-project check runs only after these branch calls finish. If the user switches projects after naming resolves but before fetchNewLaneBaseBranches runs, window.ade.git.fetch and window.ade.git.listBranches use the now-active project binding while still passing the old project's primaryLane.id. This can fetch/list refs in the wrong project and then create the lane with an incorrect base selection or a swallowed branch-list failure.

    Artifacts

    Repro: focused project drift harness

    • Contains supporting evidence from the run (text/javascript; charset=utf-8).

    Repro: harness output with structured API trace

    • Keeps the command output available without making the summary code-heavy.

    View artifacts

    T-Rex Ran code and verified through T-Rex

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/desktop/src/renderer/components/chat/AgentChatPane.tsx
    Line: 6915-6919
    
    Comment:
    **Project drift before fetch**
    
    The active-project check runs only after these branch calls finish. If the user switches projects after naming resolves but before `fetchNewLaneBaseBranches` runs, `window.ade.git.fetch` and `window.ade.git.listBranches` use the now-active project binding while still passing the old project's `primaryLane.id`. This can fetch/list refs in the wrong project and then create the lane with an incorrect base selection or a swallowed branch-list failure.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code

  3. apps/desktop/src/renderer/components/chat/AgentChatPane.tsx, line 2930-2937 (link)

    P1 Remote jobs share scope

    Draft launch jobs now live in the root store, but this key only includes projectRoot. For remote projects, selectActiveProjectRoot returns projectBinding.rootPath, so two different remote targets or project IDs with the same path, lane id, surface, and draft kind share the same job list. A ready or failed launch from one remote can appear after switching to another remote and can open or restore a stale session id in the wrong runtime.

    Artifacts

    Repro: focused harness for remote draft launch job scope collision

    • Contains supporting evidence from the run (text/javascript; charset=utf-8).

    Repro: harness output showing colliding keys and remote A job visible under remote B

    • Keeps the command output available without making the summary code-heavy.

    Repro: Vitest test attempted for the same remote draft launch scope collision

    • Contains supporting evidence from the run (text/typescript; charset=utf-8).

    Repro: Vitest attempt output showing runner startup blocker before dependency install

    • Keeps the command output available without making the summary code-heavy.

    View artifacts

    T-Rex Ran code and verified through T-Rex

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/desktop/src/renderer/components/chat/AgentChatPane.tsx
    Line: 2930-2937
    
    Comment:
    **Remote jobs share scope**
    
    Draft launch jobs now live in the root store, but this key only includes `projectRoot`. For remote projects, `selectActiveProjectRoot` returns `projectBinding.rootPath`, so two different remote targets or project IDs with the same path, lane id, surface, and draft kind share the same job list. A ready or failed launch from one remote can appear after switching to another remote and can open or restore a stale session id in the wrong runtime.
    
    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 5 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 5
apps/desktop/src/renderer/components/chat/AgentChatPane.tsx:7207-7214
**Creation remains unpinned**

`assertLaunchProjectActive()` checks the root binding, but the following `agentChat.create` / `pty.create` calls still go through unpinned preload APIs that resolve the mutable current project binding later. When the user switches projects immediately after this check, the session or PTY can be created in the newly active project with the old lane id/worktree. The rollback pin only applies after a chat session exists, so the creation itself needs to use the same pinned binding or an atomic checked-and-create path.

### Issue 2 of 5
apps/desktop/src/renderer/components/chat/AgentChatPane.tsx:7189-7195
**Timeout leaves work running**

`withDraftLaunchTimeout` only rejects the renderer-side wait; it does not cancel `resolveDraftLaunchLane`. When lane setup times out while naming or branch discovery is still in flight, the catch path marks the job failed with `targetLane === null`, but the original promise can later continue into `window.ade.lanes.create`. That creates an auto lane after the failed job has no cleanup path. The same pattern can let a timed-out session start later create/send a chat session after the job is already failed. Add a stale/abort signal that is checked before each mutation, or avoid timing out steps that can still perform irreversible work.

### Issue 3 of 5
apps/desktop/src/renderer/components/chat/AgentChatPane.tsx:6605-6611
**Orchestration cleanup unpinned**

This cleanup path still calls `agentChat.delete` without the launch pin. During a draft launch, `createSessionForLane` can create the origin-project session, the user can switch projects, and then `orchestration.runCreate` can fail. This unpinned delete resolves against the newly active project, leaving the created lead chat behind in the origin project, or deleting a same-id session in the wrong project. Thread the launch pin into `createSessionForLane` so this cleanup uses the same pinned delete path.

### Issue 4 of 5
apps/desktop/src/renderer/components/chat/AgentChatPane.tsx:6915-6919
**Project drift before fetch**

The active-project check runs only after these branch calls finish. If the user switches projects after naming resolves but before `fetchNewLaneBaseBranches` runs, `window.ade.git.fetch` and `window.ade.git.listBranches` use the now-active project binding while still passing the old project's `primaryLane.id`. This can fetch/list refs in the wrong project and then create the lane with an incorrect base selection or a swallowed branch-list failure.

### Issue 5 of 5
apps/desktop/src/renderer/components/chat/AgentChatPane.tsx:2930-2937
**Remote jobs share scope**

Draft launch jobs now live in the root store, but this key only includes `projectRoot`. For remote projects, `selectActiveProjectRoot` returns `projectBinding.rootPath`, so two different remote targets or project IDs with the same path, lane id, surface, and draft kind share the same job list. A ready or failed launch from one remote can appear after switching to another remote and can open or restore a stale session id in the wrong runtime.

Reviews (1): Last reviewed commit: "Fix lost auto-create lane / background l..." | Re-trigger Greptile

Greptile also left 2 inline comments on this PR.

An in-flight draft launch (auto-create-lane or start-in-background) from the
new-chat composer could vanish with no trace when switching projects mid-launch
— especially between two remote projects. Two root causes:

1. Job state lived in the per-project renderer store, which is torn down when
   switching to another remote project (only the active remote surface stays
   mounted), dropping the in-flight job. Hoist draft-launch job state to the
   root store so it survives surface teardown and re-surfaces on return.

2. The launch routed its mutating IPC via the mutable global project binding, so
   a mid-launch switch could create a lane/session in the WRONG project (or throw
   PROJECT_SWITCHING). The launch now captures its originating binding, aborts
   before any mutating call if the active project drifts, pins rollback deletes
   to the originating project, and times out after 90s so a dropped remote
   connection can't wedge the job in a non-terminal state.

Adds preload callPinnedRuntimeAction (optional pin on lanes.delete /
agentChat.delete), withDraftLaunchTimeout + constants in draftLaunchJobs.ts,
regression tests, and internal docs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 18, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
ade Ignored Ignored Jun 18, 2026 5:01am

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@arul28, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 22 minutes and 4 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a45ee5df-4b0a-48c5-8720-86411f8e6065

📥 Commits

Reviewing files that changed from the base of the PR and between 16f7ebb and c69e9f0.

⛔ Files ignored due to path filters (3)
  • docs/features/chat/README.md is excluded by !docs/**
  • docs/features/chat/composer-and-ui.md is excluded by !docs/**
  • docs/features/remote-runtime/internal-architecture.md is excluded by !docs/**
📒 Files selected for processing (6)
  • apps/desktop/src/preload/global.d.ts
  • apps/desktop/src/preload/preload.ts
  • apps/desktop/src/renderer/components/chat/AgentChatPane.test.tsx
  • apps/desktop/src/renderer/components/chat/AgentChatPane.tsx
  • apps/desktop/src/renderer/lib/draftLaunchJobs.test.ts
  • apps/desktop/src/renderer/lib/draftLaunchJobs.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ade/investigate-lost-auto-lane-578b6503

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.

@arul28 arul28 changed the title Investigate Lost Auto Lane Fix lost auto-create lane / background launch on project switch Jun 18, 2026
@arul28

arul28 commented Jun 18, 2026

Copy link
Copy Markdown
Owner Author

@copilot review but do not make fixes

@mintlify

mintlify Bot commented Jun 18, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
ade-ac1c6011 🟢 Ready View Preview Jun 18, 2026, 5:08 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Comment on lines +7207 to +7214
// Re-check before starting the session: a switch during prepare must not
// start a session in the now-active project.
assertLaunchProjectActive();
const launched = await withDraftLaunchTimeout(
kind === "chat"
? startDraftChatLaunch(prepared, targetLane, launchBinding)
: startDraftCliLaunch(prepared, targetLane, mode),
"Session start",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Creation remains unpinned

assertLaunchProjectActive() checks the root binding, but the following agentChat.create / pty.create calls still go through unpinned preload APIs that resolve the mutable current project binding later. When the user switches projects immediately after this check, the session or PTY can be created in the newly active project with the old lane id/worktree. The rollback pin only applies after a chat session exists, so the creation itself needs to use the same pinned binding or an atomic checked-and-create path.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/components/chat/AgentChatPane.tsx
Line: 7207-7214

Comment:
**Creation remains unpinned**

`assertLaunchProjectActive()` checks the root binding, but the following `agentChat.create` / `pty.create` calls still go through unpinned preload APIs that resolve the mutable current project binding later. When the user switches projects immediately after this check, the session or PTY can be created in the newly active project with the old lane id/worktree. The rollback pin only applies after a chat session exists, so the creation itself needs to use the same pinned binding or an atomic checked-and-create path.

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

Fix in Claude Code

Comment on lines +7189 to +7195
targetLane = await withDraftLaunchTimeout(resolveDraftLaunchLane(snapshot, () => {
patchDraftLaunchJob(jobId, { status: "creating-lane" });
}, (message) => {
patchDraftLaunchJob(jobId, { warning: message });
}, (modelId) => {
patchDraftLaunchJob(jobId, { namingModelId: modelId });
});
}, assertLaunchProjectActive), "Lane setup");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Timeout leaves work running

withDraftLaunchTimeout only rejects the renderer-side wait; it does not cancel resolveDraftLaunchLane. When lane setup times out while naming or branch discovery is still in flight, the catch path marks the job failed with targetLane === null, but the original promise can later continue into window.ade.lanes.create. That creates an auto lane after the failed job has no cleanup path. The same pattern can let a timed-out session start later create/send a chat session after the job is already failed. Add a stale/abort signal that is checked before each mutation, or avoid timing out steps that can still perform irreversible work.

Artifacts

Repro: focused timeout harness

  • Contains supporting evidence from the run (text/javascript; charset=utf-8).

Repro: harness output showing failure before late lane create

  • Keeps the command output available without making the summary code-heavy.

Repro: attempted Vitest test for the timeout path

  • Contains supporting evidence from the run (text/typescript; charset=utf-8).

Repro: Vitest attempt blocked by local Node toolchain incompatibility

  • Keeps the command output available without making the summary code-heavy.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/components/chat/AgentChatPane.tsx
Line: 7189-7195

Comment:
**Timeout leaves work running**

`withDraftLaunchTimeout` only rejects the renderer-side wait; it does not cancel `resolveDraftLaunchLane`. When lane setup times out while naming or branch discovery is still in flight, the catch path marks the job failed with `targetLane === null`, but the original promise can later continue into `window.ade.lanes.create`. That creates an auto lane after the failed job has no cleanup path. The same pattern can let a timed-out session start later create/send a chat session after the job is already failed. Add a stale/abort signal that is checked before each mutation, or avoid timing out steps that can still perform irreversible work.

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

Fix in Claude Code

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