Skip to content

fix(desktop): honor agent selection in new-workspace modal#3699

Merged
saddlepaddle merged 2 commits into
mainfrom
quark-saguaro
Apr 24, 2026
Merged

fix(desktop): honor agent selection in new-workspace modal#3699
saddlepaddle merged 2 commits into
mainfrom
quark-saguaro

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented Apr 24, 2026

Summary

  • The dashboard new-workspace modal's agent picker was cosmetic — selectedAgent was read from localStorage for the UI but never written into the pending row.
  • buildForkAgentLaunch always resolved via getFallbackAgentId, which hard-prefers claude, so selecting codex/cursor/etc. silently launched claude.
  • Threaded the selected agent through the pending row into the launch build. "none" now genuinely skips agent launch instead of falling back to claude.

Test plan

  • Open new-workspace modal, pick Codex, submit → Codex terminal launches (not Claude)
  • Pick Claude → Claude launches
  • Pick "No agent" → workspace creates, no agent launches, existing warning toast fires if prompt/files were submitted
  • Existing in-flight pending rows (null agentId) don't crash on retry — they just no-op the launch

Summary by cubic

Fixes the new-workspace modal so the agent you pick actually launches. Selecting "No agent" now truly skips agent launch; no silent fallback.

  • Bug Fixes
    • Persist selectedAgent as agentId on the pending row and thread it through dispatchForkLaunch and buildForkAgentLaunch.
    • Resolve the agent from agentId only; remove fallback to Claude. Launch only if the chosen agent is enabled, otherwise skip.
    • Handle legacy pending rows (agentId: null) safely; retries no-op without errors.

Written for commit 9bc6c38. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes
    • Agent selection is now preserved and reliably applied when creating or launching workspaces; disabled or unselected agents prevent launches.
  • New Features
    • Workspace creation and launch flows respect an explicit "no agent" option and the currently selected agent, improving predictability when forking or starting workspaces.

The modal's agent picker was cosmetic — `selectedAgent` was persisted to
localStorage for the UI but never written to the pending row, so the
pending page's `buildForkAgentLaunch` always called `getFallbackAgentId`
(which hard-prefers claude). Selecting codex, cursor, etc. silently
launched claude instead.

Thread the selected agent through the pending row into the launch build;
"none" now genuinely skips the agent launch (no silent claude
substitution).
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

📝 Walkthrough

Walkthrough

Threads a nullable agentId through UI selection, workspace creation, schema, types, and launch-building; launch logic now resolves and validates the explicit agentId (including "none") before constructing a fork agent launch.

Changes

Cohort / File(s) Summary
Schema & Types
apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts, apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/dispatchForkLaunch.ts
Add agentId to pendingWorkspaceSchema (nullable, default null) and to DispatchForkLaunchInputs.pending Pick type.
Launch Builder
apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildForkAgentLaunch.ts
Source agentId from inputs.pending.agentId, remove fallback reliance, add resolveAgentId to map/validate selection, return null for empty/"none" or disabled configs. Update BuildForkAgentLaunchInputs.pending.
Workspace Submission UI / Hook
apps/desktop/src/renderer/routes/_authenticated/components/.../PromptGroup/PromptGroup.tsx, apps/desktop/src/renderer/routes/_authenticated/components/.../useSubmitWorkspace/useSubmitWorkspace.ts
Pass selectedAgent into useSubmitWorkspace; include agentId when inserting pending workspace rows; update hook signature and callback deps.
Tests / Fixtures
apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildForkAgentLaunch.test.ts, apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildIntentPayload.test.ts
Test helpers now include agentId: null by default; tests updated to explicitly set pending.agentId where needed and add negative cases for null/"none".

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant UI as PromptGroup (UI)
    participant Hook as useSubmitWorkspace
    participant DB as PendingWorkspaces DB
    participant Launcher as buildForkAgentLaunch
    participant Resolver as resolveAgentId

    User->>UI: select agent + enter prompt
    UI->>Hook: handleSubmit(projectId, selectedAgent)
    Hook->>DB: insert pending workspace (includes agentId)
    Hook->>Launcher: call buildForkAgentLaunch(pending)
    Launcher->>Resolver: resolveAgentId(pending.agentId)
    alt agentId is valid & enabled
        Resolver->>Launcher: return matching enabled config
        Launcher->>Launcher: build launch with agent config
    else agentId null/"none" or not enabled
        Resolver->>Launcher: return null
        Launcher->>Launcher: abort/skip agent launch
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 I hopped from form to schema, neat and spry,
Chose an agent, watched the pending fly—
From UI seed to launch's final say,
Nulls and "none" now gently find their way. ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description includes a clear summary, test plan, and supplemental details. However, it is missing explicit sections matching the template's structure (Description, Related Issues, Type of Change, Testing, etc.). Restructure the description to align with the template: add explicit 'Description', 'Related Issues', 'Type of Change', and 'Testing' section headings, and move test items into the Testing section.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: fixing the agent selection in the new-workspace modal so the chosen agent is actually honored in the launch flow.
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 quark-saguaro

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.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 24, 2026

Greptile Summary

This PR fixes the new-workspace modal's agent picker being cosmetic — selectedAgent is now threaded into the pending row's agentId field and buildForkAgentLaunch uses it via the new resolveAgentId helper instead of always falling back to Claude. The core logic is correct.

  • P1 — broken tests: Adding agentId: null to pendingBase() in buildForkAgentLaunch.test.ts causes resolveAgentId to short-circuit on !selected, returning null before touching sources or configs. Four existing tests ("prompt-only → terminal launch", "linked internal task", "attachments", "chat agent") will now fail because they use the unmodified pendingBase() and expect a non-null result. Each test needs agentId: "claude" (or the appropriate ID) in its pendingBase(...) call.
  • The schema comment for agentId: null says "treated as 'use fallback'" but the code no-ops; the comment should read "treated as no-op / skip launch".

Confidence Score: 4/5

Safe to merge after fixing the broken test helpers — the production code path is correct.

The runtime fix is sound and the data flow is correctly wired end-to-end. However, four pre-existing unit tests in buildForkAgentLaunch.test.ts are broken by the agentId: null default added to pendingBase, which is a real P1 defect that should be resolved before merge to keep CI green.

apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildForkAgentLaunch.test.ts — pendingBase() tests need agentId overrides to pass

Important Files Changed

Filename Overview
apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildForkAgentLaunch.ts Core fix: replaces getFallbackAgentId with resolveAgentId(pending.agentId, configs), correctly honouring the user-selected agent; logic is sound.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildForkAgentLaunch.test.ts Adds agentId: null to pendingBase default, breaking 4 existing tests that expect non-null launch results but now receive null immediately from resolveAgentId.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/dispatchForkLaunch.ts Adds agentId to the pending pick type so it flows through to buildForkAgentLaunch; warning toast fires even when user deliberately chose "No agent".
apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts Adds agentId field (string
apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/hooks/useSubmitWorkspace/useSubmitWorkspace.ts Threads selectedAgent into the pending row insert; dependency array correctly includes selectedAgent.
apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/PromptGroup.tsx Passes selectedAgent to useSubmitWorkspace; one-line wiring change with no issues.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildIntentPayload.test.ts Adds agentId: null to the makePending factory to satisfy the updated schema type; no functional impact.

Sequence Diagram

sequenceDiagram
    participant Modal as NewWorkspaceModal
    participant Hook as useSubmitWorkspace
    participant Store as CollectionsProvider
    participant Pending as PendingPage
    participant Build as buildForkAgentLaunch
    participant Dispatch as dispatchForkLaunch

    Modal->>Hook: useSubmitWorkspace(projectId, selectedAgent)
    Hook->>Store: insert pendingRow { agentId: selectedAgent }
    Store-->>Pending: navigate /pending/:id

    Pending->>Dispatch: dispatchForkLaunch({ pending })
    Dispatch->>Build: buildForkAgentLaunch({ pending.agentId, agentConfigs })

    alt agentId === null or "none"
        Build-->>Dispatch: null
        Dispatch-->>Pending: toast warning (if userGaveInput)
    else agentId is valid and enabled
        Build-->>Dispatch: PendingLaunchBuild (terminal or chat)
        Dispatch-->>Pending: onApplyToRow({ terminalLaunch or chatLaunch })
    end
Loading

Comments Outside Diff (2)

  1. apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildForkAgentLaunch.test.ts, line 10-20 (link)

    P1 Existing tests will fail with agentId: null default

    Adding agentId: null to pendingBase causes resolveAgentId(null, configs) to return null immediately (the !selected branch), making buildForkAgentLaunch return null before checking sources or agent configs. At least four existing tests now expect a non-null result but will receive null:

    • "prompt-only → terminal launch via default agent (claude)" — expects build.launch.name === "Claude"
    • "linked internal task renders into the command" — expects terminal build
    • "attachments produce disk-ready bytes + matching names" — expects terminal build
    • "chat agent → chat launch with initialPrompt + files" — expects chat build

    Each of these tests calls pendingBase(...) without overriding agentId, so they all run with agentId: null and hit the early-return. They need to pass a valid agentId, e.g. pendingBase({ prompt: "...", agentId: "claude" }).

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildForkAgentLaunch.test.ts
    Line: 10-20
    
    Comment:
    **Existing tests will fail with `agentId: null` default**
    
    Adding `agentId: null` to `pendingBase` causes `resolveAgentId(null, configs)` to return `null` immediately (the `!selected` branch), making `buildForkAgentLaunch` return `null` before checking sources or agent configs. At least four existing tests now expect a non-null result but will receive `null`:
    
    - `"prompt-only → terminal launch via default agent (claude)"` — expects `build.launch.name === "Claude"`
    - `"linked internal task renders into the command"` — expects terminal build
    - `"attachments produce disk-ready bytes + matching names"` — expects terminal build
    - `"chat agent → chat launch with initialPrompt + files"` — expects chat build
    
    Each of these tests calls `pendingBase(...)` without overriding `agentId`, so they all run with `agentId: null` and hit the early-return. They need to pass a valid `agentId`, e.g. `pendingBase({ prompt: "...", agentId: "claude" })`.
    
    How can I resolve this? If you propose a fix, please make it concise.
  2. apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/dispatchForkLaunch.ts, line 101-119 (link)

    P2 Warning toast fires when user explicitly chose "No agent"

    When the user picks "No agent" (agentId: "none") and submits with a prompt or attachments, buildForkAgentLaunch returns null and userGaveInput is true, so the toast "Workspace created but no agent launched / Enable an agent in Settings → Agents to auto-launch on new workspaces" is shown. This is confusing UX — the user already made a deliberate choice; the "enable an agent in Settings" tip is misleading for them.

    Consider suppressing the toast when the user explicitly opted out:

    const userGaveInput = pending.agentId !== "none" && (
        (pending.prompt?.trim().length ?? 0) > 0 ||
        pending.linkedIssues.length > 0 ||
        !!pending.linkedPR ||
        (loadedAttachments?.length ?? 0) > 0
    );
    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/dispatchForkLaunch.ts
    Line: 101-119
    
    Comment:
    **Warning toast fires when user explicitly chose "No agent"**
    
    When the user picks "No agent" (`agentId: "none"`) and submits with a prompt or attachments, `buildForkAgentLaunch` returns `null` and `userGaveInput` is `true`, so the toast "Workspace created but no agent launched / Enable an agent in Settings → Agents to auto-launch on new workspaces" is shown. This is confusing UX — the user already made a deliberate choice; the "enable an agent in Settings" tip is misleading for them.
    
    Consider suppressing the toast when the user explicitly opted out:
    ```ts
    const userGaveInput = pending.agentId !== "none" && (
        (pending.prompt?.trim().length ?? 0) > 0 ||
        pending.linkedIssues.length > 0 ||
        !!pending.linkedPR ||
        (loadedAttachments?.length ?? 0) > 0
    );
    ```
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildForkAgentLaunch.test.ts
Line: 10-20

Comment:
**Existing tests will fail with `agentId: null` default**

Adding `agentId: null` to `pendingBase` causes `resolveAgentId(null, configs)` to return `null` immediately (the `!selected` branch), making `buildForkAgentLaunch` return `null` before checking sources or agent configs. At least four existing tests now expect a non-null result but will receive `null`:

- `"prompt-only → terminal launch via default agent (claude)"` — expects `build.launch.name === "Claude"`
- `"linked internal task renders into the command"` — expects terminal build
- `"attachments produce disk-ready bytes + matching names"` — expects terminal build
- `"chat agent → chat launch with initialPrompt + files"` — expects chat build

Each of these tests calls `pendingBase(...)` without overriding `agentId`, so they all run with `agentId: null` and hit the early-return. They need to pass a valid `agentId`, e.g. `pendingBase({ prompt: "...", agentId: "claude" })`.

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

---

This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts
Line: 188-191

Comment:
**Schema comment contradicts actual runtime behavior**

The comment says `null` is "treated as 'use fallback'" but `resolveAgentId` does the opposite — `!selected` (which is true for `null`) immediately returns `null`, skipping the launch entirely. A legacy row with `agentId: null` and a prompt will show the "Workspace created but no agent launched" warning toast, not fall back to Claude as the comment implies. The comment should reflect the actual no-op semantics.

```suggestion
	// User-selected agent from the modal. `"none"` = user explicitly chose not
	// to launch; any other string = `AgentDefinitionId`; null = legacy rows
	// (predating this field), treated as no-op (launch is skipped).
	agentId: z.string().nullable().default(null),
```

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

---

This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/dispatchForkLaunch.ts
Line: 101-119

Comment:
**Warning toast fires when user explicitly chose "No agent"**

When the user picks "No agent" (`agentId: "none"`) and submits with a prompt or attachments, `buildForkAgentLaunch` returns `null` and `userGaveInput` is `true`, so the toast "Workspace created but no agent launched / Enable an agent in Settings → Agents to auto-launch on new workspaces" is shown. This is confusing UX — the user already made a deliberate choice; the "enable an agent in Settings" tip is misleading for them.

Consider suppressing the toast when the user explicitly opted out:
```ts
const userGaveInput = pending.agentId !== "none" && (
    (pending.prompt?.trim().length ?? 0) > 0 ||
    pending.linkedIssues.length > 0 ||
    !!pending.linkedPR ||
    (loadedAttachments?.length ?? 0) > 0
);
```

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

Reviews (1): Last reviewed commit: "fix(desktop): honor agent selection in n..." | Re-trigger Greptile

Comment on lines +188 to +191
// User-selected agent from the modal. `"none"` = user explicitly chose not
// to launch; any other string = `AgentDefinitionId`; null = legacy rows
// (predating this field), treated as "use fallback".
agentId: z.string().nullable().default(null),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Schema comment contradicts actual runtime behavior

The comment says null is "treated as 'use fallback'" but resolveAgentId does the opposite — !selected (which is true for null) immediately returns null, skipping the launch entirely. A legacy row with agentId: null and a prompt will show the "Workspace created but no agent launched" warning toast, not fall back to Claude as the comment implies. The comment should reflect the actual no-op semantics.

Suggested change
// User-selected agent from the modal. `"none"` = user explicitly chose not
// to launch; any other string = `AgentDefinitionId`; null = legacy rows
// (predating this field), treated as "use fallback".
agentId: z.string().nullable().default(null),
// User-selected agent from the modal. `"none"` = user explicitly chose not
// to launch; any other string = `AgentDefinitionId`; null = legacy rows
// (predating this field), treated as no-op (launch is skipped).
agentId: z.string().nullable().default(null),
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts
Line: 188-191

Comment:
**Schema comment contradicts actual runtime behavior**

The comment says `null` is "treated as 'use fallback'" but `resolveAgentId` does the opposite — `!selected` (which is true for `null`) immediately returns `null`, skipping the launch entirely. A legacy row with `agentId: null` and a prompt will show the "Workspace created but no agent launched" warning toast, not fall back to Claude as the comment implies. The comment should reflect the actual no-op semantics.

```suggestion
	// User-selected agent from the modal. `"none"` = user explicitly chose not
	// to launch; any other string = `AgentDefinitionId`; null = legacy rows
	// (predating this field), treated as no-op (launch is skipped).
	agentId: z.string().nullable().default(null),
```

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

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 7 files

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildForkAgentLaunch.test.ts (1)

10-21: ⚠️ Potential issue | 🔴 Critical

Fix test fixture: pendingBase() defaults agentId to null, causing test failures

pendingBase() sets agentId: null, which causes buildForkAgentLaunch() to return null at line 130. However, tests at lines 125–138, 140–158, 160–179, and 188–206 don't override agentId and expect non-null builds (e.g., expect(build?.kind).toBe("terminal")). With agentId: null, these assertions will fail.

Update each affected test to explicitly set agentId:

Examples
- pending: pendingBase({ prompt: "refactor the auth middleware" }),
+ pending: pendingBase({ prompt: "refactor the auth middleware", agentId: "claude" }),
- pending: pendingBase({
+ pending: pendingBase({
+   agentId: "claude",
    prompt: "do it",
    linkedIssues: [ ... ],
  }),
- pending: pendingBase({ prompt: "help me refactor" }),
+ pending: pendingBase({ prompt: "help me refactor", agentId: "superset-chat" }),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/buildForkAgentLaunch.test.ts
around lines 10 - 21, The fixture pendingBase currently defaults agentId to null
which causes buildForkAgentLaunch to return null; update the failing tests that
call pendingBase() without overrides to explicitly set a non-null agentId (e.g.,
pass { agentId: "agent-123" } or use any existing AGENT_ID constant) when
constructing inputs for buildLaunchSourcesFromPending so buildForkAgentLaunch
produces a non-null build; locate usages in the tests that call pendingBase()
and add the agentId override or alternatively change pendingBase's default
agentId to a valid test agent id, ensuring assertions like
expect(build?.kind).toBe("terminal") receive a non-null build.
🧹 Nitpick comments (3)
apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts (1)

191-191: Consider narrowing agentId to a validated union.

z.string().nullable() accepts arbitrary strings, while the producer side (WorkspaceCreateAgent in PromptGroup.tsx) is "none" | AgentDefinitionId. Since the downstream consumer (resolveAgentId) already casts via as AgentDefinitionId, a tighter schema (e.g. z.union([z.literal("none"), z.string()]).nullable() or the explicit AgentDefinitionId enum if exported) would better document the contract and catch malformed persisted rows at parse time. Non-blocking — current shape is intentionally permissive to tolerate future agent IDs.

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

In
`@apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts`
at line 191, The schema's agentId field is currently z.string().nullable() which
allows arbitrary strings; change it to a tighter union that matches the
producer/consumer contract (e.g. use z.union([z.literal("none"),
z.string()]).nullable() or, if available, the exported AgentDefinitionId
enum/brand) so invalid persisted agent IDs are caught at parse time; update the
agentId definition in schema.ts (referencing agentId), ensure consistency with
WorkspaceCreateAgent in PromptGroup.tsx and resolveAgentId usage, and run the
existing validation/tests to confirm no regressions.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildForkAgentLaunch.ts (2)

129-135: Redundant re-lookup of agentConfig after resolveAgentId.

resolveAgentId already calls indexResolvedAgentConfigs(configs) and verifies match.enabled before returning match.id. The subsequent call here re-indexes the same list and re-checks !agentConfig.enabled. Consider having resolveAgentId return the resolved config (or a { id, config } tuple) so you avoid the second indexResolvedAgentConfigs pass and the redundant guard.

♻️ Proposed refactor
-	const agentId = resolveAgentId(inputs.pending.agentId, inputs.agentConfigs);
-	if (!agentId) return null;
-
-	const agentConfig = indexResolvedAgentConfigs(inputs.agentConfigs).get(
-		agentId,
-	);
-	if (!agentConfig || !agentConfig.enabled) return null;
+	const agentConfig = resolveAgentConfig(
+		inputs.pending.agentId,
+		inputs.agentConfigs,
+	);
+	if (!agentConfig) return null;
+	const agentId = agentConfig.id;
-function resolveAgentId(
+function resolveAgentConfig(
 	selected: string | null,
 	configs: ResolvedAgentConfig[],
-): AgentDefinitionId | null {
+): ResolvedAgentConfig | null {
 	if (!selected || selected === "none") return null;
 	const match = indexResolvedAgentConfigs(configs).get(
 		selected as AgentDefinitionId,
 	);
-	return match?.enabled ? match.id : null;
+	return match?.enabled ? match : null;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/buildForkAgentLaunch.ts
around lines 129 - 135, resolveAgentId is redoing indexResolvedAgentConfigs and
the enabled check, so avoid the duplicate work by changing resolveAgentId to
return both the resolved id and its config (e.g., { id, config } or a tuple)
instead of only id; then in buildForkAgentLaunch.ts replace the current agentId
= resolveAgentId(...) call with a destructuring of the returned { id, config },
remove the subsequent indexResolvedAgentConfigs(...) lookup and the duplicated
enabled guard (use the returned config to null-check), and adjust downstream
uses from agentId/agentConfig accordingly.

167-176: Cast of user-supplied selected to AgentDefinitionId is functionally safe but could be avoided.

selected is typed as string | null (matching the schema's z.string().nullable()), but is cast to AgentDefinitionId before the .get(...) lookup. Since indexResolvedAgentConfigs returns a Map<AgentDefinitionId, ResolvedAgentConfig> and Map.get safely returns undefined for unknown keys, the cast is harmless. However, it silently accepts any string persisted in legacy rows.

To tighten this, the schema could use z.string().refine() with the exported isBuiltinAgentId() or isCustomAgentId() helpers (from @superset/shared/agent-catalog), which would validate the actual AgentDefinitionId union at the source and eliminate the need for the cast.

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

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/buildForkAgentLaunch.ts
around lines 167 - 176, The resolveAgentId function currently casts the
user-supplied selected string to AgentDefinitionId before looking it up in the
Map returned by indexResolvedAgentConfigs; instead, validate the value first
using the exported helpers and avoid the cast: either update the input schema to
use z.string().refine(...) with isBuiltinAgentId/isCustomAgentId so selected is
typed as AgentDefinitionId upstream, or add a runtime guard in resolveAgentId
that calls isBuiltinAgentId(selected) || isCustomAgentId(selected) and only then
calls indexResolvedAgentConfigs(configs).get(selected) to return match?.enabled
? match.id : null; this removes the unsafe cast and tightens validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts`:
- Around line 188-191: The schema comment for agentId is misleading: instead of
saying "null = legacy rows ... treated as 'use fallback'", update the comment to
state that null indicates legacy/in-flight rows that should be treated as a
no-op (no launch) because resolveAgentId returns null and buildForkAgentLaunch
will return null; reference the agentId field in this file and the
resolveAgentId and buildForkAgentLaunch functions so maintainers understand the
actual downstream behavior.

---

Outside diff comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/buildForkAgentLaunch.test.ts:
- Around line 10-21: The fixture pendingBase currently defaults agentId to null
which causes buildForkAgentLaunch to return null; update the failing tests that
call pendingBase() without overrides to explicitly set a non-null agentId (e.g.,
pass { agentId: "agent-123" } or use any existing AGENT_ID constant) when
constructing inputs for buildLaunchSourcesFromPending so buildForkAgentLaunch
produces a non-null build; locate usages in the tests that call pendingBase()
and add the agentId override or alternatively change pendingBase's default
agentId to a valid test agent id, ensuring assertions like
expect(build?.kind).toBe("terminal") receive a non-null build.

---

Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/buildForkAgentLaunch.ts:
- Around line 129-135: resolveAgentId is redoing indexResolvedAgentConfigs and
the enabled check, so avoid the duplicate work by changing resolveAgentId to
return both the resolved id and its config (e.g., { id, config } or a tuple)
instead of only id; then in buildForkAgentLaunch.ts replace the current agentId
= resolveAgentId(...) call with a destructuring of the returned { id, config },
remove the subsequent indexResolvedAgentConfigs(...) lookup and the duplicated
enabled guard (use the returned config to null-check), and adjust downstream
uses from agentId/agentConfig accordingly.
- Around line 167-176: The resolveAgentId function currently casts the
user-supplied selected string to AgentDefinitionId before looking it up in the
Map returned by indexResolvedAgentConfigs; instead, validate the value first
using the exported helpers and avoid the cast: either update the input schema to
use z.string().refine(...) with isBuiltinAgentId/isCustomAgentId so selected is
typed as AgentDefinitionId upstream, or add a runtime guard in resolveAgentId
that calls isBuiltinAgentId(selected) || isCustomAgentId(selected) and only then
calls indexResolvedAgentConfigs(configs).get(selected) to return match?.enabled
? match.id : null; this removes the unsafe cast and tightens validation.

In
`@apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts`:
- Line 191: The schema's agentId field is currently z.string().nullable() which
allows arbitrary strings; change it to a tighter union that matches the
producer/consumer contract (e.g. use z.union([z.literal("none"),
z.string()]).nullable() or, if available, the exported AgentDefinitionId
enum/brand) so invalid persisted agent IDs are caught at parse time; update the
agentId definition in schema.ts (referencing agentId), ensure consistency with
WorkspaceCreateAgent in PromptGroup.tsx and resolveAgentId usage, and run the
existing validation/tests to confirm no regressions.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: a6ca0d19-2e0a-4e6a-9dc3-0095839d96ea

📥 Commits

Reviewing files that changed from the base of the PR and between 8ce1519 and e17388b.

📒 Files selected for processing (7)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildForkAgentLaunch.test.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildForkAgentLaunch.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildIntentPayload.test.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/dispatchForkLaunch.ts
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/PromptGroup.tsx
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/hooks/useSubmitWorkspace/useSubmitWorkspace.ts
  • apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts

Comment on lines +188 to +191
// User-selected agent from the modal. `"none"` = user explicitly chose not
// to launch; any other string = `AgentDefinitionId`; null = legacy rows
// (predating this field), treated as "use fallback".
agentId: z.string().nullable().default(null),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Comment contradicts actual null-handling behavior.

The comment states null = legacy rows (predating this field), treated as "use fallback", but downstream resolveAgentId in buildForkAgentLaunch.ts returns null when selected is null, which causes buildForkAgentLaunch to return null (no launch). This matches the PR's stated behavior ("existing in-flight pending rows with null agentId … no-op the launch rather than crashing") but directly contradicts "use fallback" in this schema comment.

Recommend aligning the comment with the actual no-op behavior so future maintainers don't expect a fallback resolution path.

📝 Proposed comment fix
-	// User-selected agent from the modal. `"none"` = user explicitly chose not
-	// to launch; any other string = `AgentDefinitionId`; null = legacy rows
-	// (predating this field), treated as "use fallback".
+	// User-selected agent from the modal. `"none"` = user explicitly chose not
+	// to launch; any other string = `AgentDefinitionId`; null = legacy rows
+	// (predating this field) — treated the same as `"none"` (no-op launch on
+	// retry) rather than falling back to a default agent.
 	agentId: z.string().nullable().default(null),
📝 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
// User-selected agent from the modal. `"none"` = user explicitly chose not
// to launch; any other string = `AgentDefinitionId`; null = legacy rows
// (predating this field), treated as "use fallback".
agentId: z.string().nullable().default(null),
// User-selected agent from the modal. `"none"` = user explicitly chose not
// to launch; any other string = `AgentDefinitionId`; null = legacy rows
// (predating this field) — treated the same as `"none"` (no-op launch on
// retry) rather than falling back to a default agent.
agentId: z.string().nullable().default(null),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts`
around lines 188 - 191, The schema comment for agentId is misleading: instead of
saying "null = legacy rows ... treated as 'use fallback'", update the comment to
state that null indicates legacy/in-flight rows that should be treated as a
no-op (no launch) because resolveAgentId returns null and buildForkAgentLaunch
will return null; reference the agentId field in this file and the
resolveAgentId and buildForkAgentLaunch functions so maintainers understand the
actual downstream behavior.

…election

Tests were written against the old fallback-to-claude behavior — they
passed `agentId: null` and expected Claude to launch. Under the new
contract, null is treated as "no selection → no launch".

Pass explicit `agentId` in each case. Add coverage for the new null /
"none" paths.
Copy link
Copy Markdown
Contributor

@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.

🧹 Nitpick comments (1)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildForkAgentLaunch.test.ts (1)

210-236: Negative-case coverage looks solid.

The three cases now cleanly separate the reasons a launch may resolve to null:

  • disabled selection (line 210) — selection exists but agent is disabled,
  • no selection (line 220) — agentId: null,
  • explicit opt-out (line 229) — agentId: "none".

Nit (optional): consider adding a positive assertion in the "none" test that buildLaunchSourcesFromPending still produces sources for the same pending, to pin down that the null result comes from the agent-resolution step and not from an empty source list. Not blocking.

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

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/buildForkAgentLaunch.test.ts
around lines 210 - 236, Add a positive assertion in the 'agentId "none" → null
(explicit opt-out)' test to ensure the null return is due to agent resolution
and not empty sources: call buildLaunchSourcesFromPending with the same pending
(pendingBase({ prompt: "hi", agentId: "none" })) and assert it returns a
non-empty array (or expected sources) before asserting buildForkAgentLaunch(...)
is null; update the test to reference buildForkAgentLaunch and
buildLaunchSourcesFromPending so reviewers can see the source list is present
while the final launch is null.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/buildForkAgentLaunch.test.ts:
- Around line 210-236: Add a positive assertion in the 'agentId "none" → null
(explicit opt-out)' test to ensure the null return is due to agent resolution
and not empty sources: call buildLaunchSourcesFromPending with the same pending
(pendingBase({ prompt: "hi", agentId: "none" })) and assert it returns a
non-empty array (or expected sources) before asserting buildForkAgentLaunch(...)
is null; update the test to reference buildForkAgentLaunch and
buildLaunchSourcesFromPending so reviewers can see the source list is present
while the final launch is null.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6fe38300-b6c6-47d1-adbe-3ed9ccb121ec

📥 Commits

Reviewing files that changed from the base of the PR and between e17388b and 9bc6c38.

📒 Files selected for processing (1)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildForkAgentLaunch.test.ts

@saddlepaddle saddlepaddle merged commit f59eeba into main Apr 24, 2026
7 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

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